From 9a913ea2c2e862d9cbe1722318abd0bed759e9fb Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Thu, 21 Nov 2024 10:16:16 +1100 Subject: [PATCH] Rework GraphQL schema and IR parsing for boolean expressions to fix issues with nested fields (#1361) ### What This PR reworks the GraphQL schema annotation types and IR parsing for boolean expressions. This was required because the existing code was quite tangled and broken as it did not properly handled nested fields. For example, it had the following issues: * If you use more than one nested field in a single where, an invalid NDC query is generated with mangled field paths * If you use logical operators inside a nested field, an internal error occurs The new annotation structure and IR parsing code cleanly matches the two types of boolean expressions we currently have: object boolean expressions and scalar boolean expressions. The new parsing code correctly tracks nested fields and allows arbitrary nesting of object boolean expressions (and therefore logical operators at the object boolean expression level). ### How In `crates/graphql/schema/src/types.rs` `BooleanExpressionAnnotation` has been reworked to capture the two different types of boolean expressions. It now has the two `ObjectBooleanExpressionField` and `ScalarBooleanExpressionField` variants, which are used to denote fields on each of the different types of boolean expression objects. Previously scalar boolean expression annotations lived on `ModelInputAnnotation`, muddying the waters. Then in `crates/graphql/ir/src/filter.rs`, we have `resolve_object_boolean_expression` and `resolve_scalar_boolean_expression` which are capable of reading the respective variants of boolean expressions. `resolve_object_boolean_expression` is recursive, and so nested logical operators are handled naturally. As nested fields are descended through, this is kept track of with `column_path`. Some new fields were added to the existing nested object test (`crates/engine/tests/execute/models/select_many/where/nested_select/object/request.gql`) to capture and test the previously broken scenarios. A minor fix was made to `crates/graphql/frontend/src/to_opendd_ir.rs` because it did not handle multiple root fields properly, as it did not use the root field aliases and ended up overwriting the same query over and over. This resulted in many snapshots getting updated, even though they are unrelated to the real change in this PR. V3_GIT_ORIGIN_REV_ID: f7cbbe8c86f542150e0ce247c6597df717836c06 --- v3/Dockerfile | 2 +- v3/changelog.md | 5 + .../where/nested_select/common-metadata.json | 110 +++- .../where/nested_select/object/expected.json | 25 + .../where/nested_select/object/request.gql | 32 + ...el_argument_presets_select_many_admin.snap | 76 +++ ...l_argument_presets_select_many_user_1.snap | 76 +++ ...l_argument_presets_select_many_user_2.snap | 76 +++ ...ds__object_type_input_arguments_admin.snap | 70 ++- ...elect_many__limit_offset__limit_admin.snap | 124 +++- ...any__limit_offset__limit_offset_admin.snap | 254 +++++++- ...ny__limit_offset__limit_offset_user_1.snap | 254 +++++++- ...ny__limit_offset__limit_offset_user_2.snap | 254 +++++++- ...lect_many__limit_offset__limit_user_1.snap | 124 +++- ...lect_many__limit_offset__limit_user_2.snap | 124 +++- ...lect_many__limit_offset__offset_admin.snap | 124 +++- ...ect_many__limit_offset__offset_user_1.snap | 124 +++- ...ect_many__limit_offset__offset_user_2.snap | 124 +++- ..._limit_offset__unexpected_value_admin.snap | 61 +- ...limit_offset__unexpected_value_user_1.snap | 61 +- ...limit_offset__unexpected_value_user_2.snap | 61 +- ..._select_many__order_by__filter_user_1.snap | 71 +++ ...__select_many__order_by__nested_admin.snap | 77 ++- ...__order_by__with_graphql_config_admin.snap | 101 ++- ...y__order_by__with_graphql_config_user.snap | 101 ++- ...t_many__order_by__with_model_v2_admin.snap | 85 ++- ...__models__select_many__order_by_admin.snap | 85 ++- ...many__select_with_args__filter_user_1.snap | 2 +- ..._with_args__with_graphql_config_admin.snap | 71 ++- ...t_with_args__with_graphql_config_user.snap | 71 ++- ...__select_many__select_with_args_admin.snap | 69 ++- ..._where__boolean_expression_type_admin.snap | 450 +++++++++++++- ...__where__boolean_expression_type_user.snap | 450 +++++++++++++- ...lect_many__where__ndc_operators_admin.snap | 268 +++++++- ...ect_many__where__ndc_operators_user_1.snap | 268 +++++++- ...ect_many__where__ndc_operators_user_2.snap | 268 +++++++- ...ny__where__nested_select__array_admin.snap | 75 ++- ...y__where__nested_select__object_admin.snap | 301 ++++++++- ...here__shared_boolean_expression_admin.snap | 450 +++++++++++++- ...where__shared_boolean_expression_user.snap | 450 +++++++++++++- ...els__select_many__where__simple_admin.snap | 225 ++++++- ...dels__select_many__where__simple_user.snap | 225 ++++++- ...any__where__with_graphql_config_admin.snap | 150 ++++- ...many__where__with_graphql_config_user.snap | 150 ++++- ...lect_many__limit_offset__offset_admin.snap | 11 +- ...ect_many__limit_offset__offset_user_1.snap | 7 +- ...ect_many__limit_offset__offset_user_2.snap | 7 +- v3/crates/execute/src/query_usage.rs | 19 +- .../graphql/frontend/src/to_opendd_ir.rs | 12 +- v3/crates/graphql/ir/src/filter.rs | 574 ++++++++---------- v3/crates/graphql/ir/src/model_selection.rs | 2 +- .../graphql/ir/src/query_root/select_many.rs | 2 +- v3/crates/graphql/ir/src/relationship.rs | 2 +- .../graphql/schema/src/boolean_expression.rs | 140 ++++- v3/crates/graphql/schema/src/lib.rs | 11 +- v3/crates/graphql/schema/src/model_filter.rs | 92 +-- v3/crates/graphql/schema/src/types.rs | 76 ++- .../src/helpers/boolean_expression.rs | 2 +- .../src/stages/boolean_expressions/graphql.rs | 2 +- .../src/stages/boolean_expressions/object.rs | 8 +- .../src/stages/boolean_expressions/types.rs | 4 +- 61 files changed, 7020 insertions(+), 575 deletions(-) diff --git a/v3/Dockerfile b/v3/Dockerfile index 96fc3dd7dd5..2d9d2a56643 100644 --- a/v3/Dockerfile +++ b/v3/Dockerfile @@ -1,5 +1,5 @@ # This should match the Rust version in rust-toolchain.yaml and the other Dockerfiles. -FROM rust:1.82.0 as chef +FROM rust:1.82.0 AS chef WORKDIR app diff --git a/v3/changelog.md b/v3/changelog.md index 7dfafcb2fb1..4a1cb3ef130 100644 --- a/v3/changelog.md +++ b/v3/changelog.md @@ -6,6 +6,11 @@ ### Fixed +- Fixed an error that occurred when filtering by more than one nested field at a + time. +- Fixed an error that occurred when filtering using logical operators (eg + `_and`) from inside a nested field. + ### Changed ## [v2024.11.18] diff --git a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/common-metadata.json b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/common-metadata.json index 3489261215f..f4443c11214 100644 --- a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/common-metadata.json +++ b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/common-metadata.json @@ -96,6 +96,10 @@ { "fieldName": "staff", "booleanExpressionType": "staff_bool_exp" + }, + { + "fieldName": "songs", + "booleanExpressionType": "institution_songs_bool_exp" } ], "comparableRelationships": [] @@ -112,6 +116,38 @@ } } }, + { + "kind": "BooleanExpressionType", + "version": "v1", + "definition": { + "name": "institution_songs_bool_exp", + "operand": { + "object": { + "type": "institution_songs", + "comparableFields": [ + { + "fieldName": "primaryAnthemTrackId", + "booleanExpressionType": "int_bool_exp" + }, + { + "fieldName": "secondaryAnthemTrackId", + "booleanExpressionType": "int_bool_exp" + } + ], + "comparableRelationships": [] + } + }, + "logicalOperators": { + "enable": true + }, + "isNull": { + "enable": true + }, + "graphql": { + "typeName": "InstitutionSongsBoolExp" + } + } + }, { "kind": "BooleanExpressionType", "version": "v1", @@ -209,6 +245,10 @@ { "name": "departments", "type": "[String]" + }, + { + "name": "songs", + "type": "institution_songs" } ], "graphql": { @@ -243,6 +283,11 @@ "column": { "name": "departments" } + }, + "songs": { + "column": { + "name": "songs" + } } } } @@ -263,7 +308,64 @@ "name", "location", "staff", - "departments" + "departments", + "songs" + ] + } + } + ] + } + }, + { + "kind": "ObjectType", + "version": "v1", + "definition": { + "name": "institution_songs", + "fields": [ + { + "name": "primaryAnthemTrackId", + "type": "Int" + }, + { + "name": "secondaryAnthemTrackId", + "type": "Int" + } + ], + "graphql": { + "typeName": "InstitutionSongs" + }, + "dataConnectorTypeMapping": [ + { + "dataConnectorName": "db", + "dataConnectorObjectType": "institution_songs", + "fieldMapping": { + "primaryAnthemTrackId": { + "column": { + "name": "primary_anthem_track_id" + } + }, + "secondaryAnthemTrackId": { + "column": { + "name": "secondary_anthem_track_id" + } + } + } + } + ] + } + }, + { + "kind": "TypePermissions", + "version": "v1", + "definition": { + "typeName": "institution_songs", + "permissions": [ + { + "role": "admin", + "output": { + "allowedFields": [ + "primaryAnthemTrackId", + "secondaryAnthemTrackId" ] } } @@ -627,6 +729,12 @@ "orderByDirections": { "enableAll": true } + }, + { + "fieldName": "songs", + "orderByDirections": { + "enableAll": true + } } ] } diff --git a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/expected.json b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/expected.json index 08bcd0d3139..6b9f90a978c 100644 --- a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/expected.json +++ b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/expected.json @@ -70,6 +70,31 @@ "Medicine and Dentistry" ] } + ], + "match_two_nested_fields": [ + { + "id": 1, + "songs": { + "primaryAnthemTrackId": 2270, + "secondaryAnthemTrackId": 2271 + } + } + ], + "nested_logical_operators": [ + { + "id": 1, + "songs": { + "primaryAnthemTrackId": 2270, + "secondaryAnthemTrackId": 2271 + } + }, + { + "id": 2, + "songs": { + "primaryAnthemTrackId": 3421, + "secondaryAnthemTrackId": null + } + } ] } }, diff --git a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/request.gql b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/request.gql index 1a5e2e852b9..05c2fc917c6 100644 --- a/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/request.gql +++ b/v3/crates/engine/tests/execute/models/select_many/where/nested_select/object/request.gql @@ -44,4 +44,36 @@ query MyQuery { } departments } + + match_two_nested_fields: InstitutionMany( + where: { + songs: { + primaryAnthemTrackId: { _eq: 2270 } + secondaryAnthemTrackId: { _eq: 2271 } + } + } + ) { + id + songs { + primaryAnthemTrackId + secondaryAnthemTrackId + } + } + + nested_logical_operators: InstitutionMany( + where: { + songs: { + _or: [ + { primaryAnthemTrackId: { _eq: 2270 } } + { primaryAnthemTrackId: { _eq: 3421 } } + ] + } + } + ) { + id + songs { + primaryAnthemTrackId + secondaryAnthemTrackId + } + } } diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_admin.snap index 0b72f4e094c..bbe4331f3d6 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_admin.snap @@ -81,6 +81,82 @@ V1( }, }, ), + Alias( + Identifier( + "noargs", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ActorsByMovie", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "actor_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "actor_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "movie_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "movie_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_1.snap index 0b72f4e094c..bbe4331f3d6 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_1.snap @@ -81,6 +81,82 @@ V1( }, }, ), + Alias( + Identifier( + "noargs", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ActorsByMovie", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "actor_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "actor_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "movie_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "movie_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_2.snap index 0b72f4e094c..bbe4331f3d6 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__functions__model_argument_presets_select_many_user_2.snap @@ -81,6 +81,82 @@ V1( }, }, ), + Alias( + Identifier( + "noargs", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ActorsByMovie", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "actor_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "actor_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "movie_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "movie_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__object_type_input_arguments_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__object_type_input_arguments_admin.snap index 2fe225cafb2..386b59e0dad 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__object_type_input_arguments_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__commands__object_type_input_arguments_admin.snap @@ -7,7 +7,75 @@ V1( queries: { Alias( Identifier( - "getActorsByName", + "leonardoDiCaprio", + ), + ): Command( + CommandSelection { + target: CommandTarget { + subgraph: SubgraphName( + "default", + ), + command_name: CommandName( + Identifier( + "get_actors_by_name", + ), + ), + arguments: { + ArgumentName( + Identifier( + "name_filter", + ), + ): Literal( + Object { + "first_name": String("Leonardo"), + "surname": String("DiCaprio"), + }, + ), + }, + }, + selection: Some( + { + Alias( + Identifier( + "movie_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "movie_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + ), + }, + ), + Alias( + Identifier( + "kate", ), ): Command( CommandSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_admin.snap index c050c195b2e..261966da598 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_admin.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimitZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_admin.snap index 3e6bdd76091..fab89dba832 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_admin.snap @@ -7,7 +7,259 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimit0Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 2, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset2", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_1.snap index 3e6bdd76091..fab89dba832 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_1.snap @@ -7,7 +7,259 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimit0Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 2, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset2", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_2.snap index 3e6bdd76091..fab89dba832 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_offset_user_2.snap @@ -7,7 +7,259 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimit0Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset0", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit1Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 2, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimit2Offset2", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_1.snap index c050c195b2e..261966da598 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_1.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimitZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_2.snap index c050c195b2e..261966da598 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__limit_user_2.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithLimitZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 0, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithLimitTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_admin.snap index 85145a5d5f2..244faba4ec3 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_admin.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithOffsetZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_1.snap index 85145a5d5f2..244faba4ec3 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_1.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithOffsetZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_2.snap index 85145a5d5f2..244faba4ec3 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__offset_user_2.snap @@ -7,7 +7,129 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyWithOffsetZero", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 0, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetOne", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithOffsetTwo", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_admin.snap index aa3c8960f9a..0162dc7faaa 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_admin.snap @@ -7,7 +7,66 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNegativeLimit", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithNegativeOffset", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_1.snap index aa3c8960f9a..0162dc7faaa 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_1.snap @@ -7,7 +7,66 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNegativeLimit", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithNegativeOffset", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_2.snap index aa3c8960f9a..0162dc7faaa 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__limit_offset__unexpected_value_user_2.snap @@ -7,7 +7,66 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNegativeLimit", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: {}, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyWithNegativeOffset", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__filter_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__filter_user_1.snap index ec7153a1abb..e2e628bb0aa 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__filter_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__filter_user_1.snap @@ -9,6 +9,77 @@ V1( Identifier( "AuthorMany", ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "order_by", + ), + ): Literal( + Array [ + Object { + "first_name": String("Asc"), + }, + ], + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "a1", + ), ): Model( ModelSelection { target: ModelTarget { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__nested_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__nested_admin.snap index d8bba634c96..d87dd1dcd92 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__nested_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__nested_admin.snap @@ -7,7 +7,82 @@ V1( queries: { Alias( Identifier( - "InstitutionMany", + "asc", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "institutions", + ), + ), + arguments: { + ArgumentName( + Identifier( + "order_by", + ), + ): Literal( + Array [ + Object { + "location": Array [ + Object { + "city": String("Asc"), + }, + ], + }, + ], + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "location", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "location", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "desc", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_admin.snap index e8a0eb3c32b..48c7237e10e 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_admin.snap @@ -21,20 +21,6 @@ V1( ), ), arguments: { - ArgumentName( - Identifier( - "limit_custom", - ), - ): Literal( - Number(1), - ), - ArgumentName( - Identifier( - "offset_custom", - ), - ): Literal( - Number(1), - ), ArgumentName( Identifier( "order_by_custom", @@ -42,7 +28,7 @@ V1( ): Literal( Array [ Object { - "first_name": String("Desc_custom"), + "first_name": String("Asc_custom"), }, ], ), @@ -153,6 +139,91 @@ V1( }, }, ), + Alias( + Identifier( + "a1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "limit_custom", + ), + ): Literal( + Number(1), + ), + ArgumentName( + Identifier( + "offset_custom", + ), + ): Literal( + Number(1), + ), + ArgumentName( + Identifier( + "order_by_custom", + ), + ): Literal( + Array [ + Object { + "first_name": String("Desc_custom"), + }, + ], + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_user.snap index e8a0eb3c32b..48c7237e10e 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_graphql_config_user.snap @@ -21,20 +21,6 @@ V1( ), ), arguments: { - ArgumentName( - Identifier( - "limit_custom", - ), - ): Literal( - Number(1), - ), - ArgumentName( - Identifier( - "offset_custom", - ), - ): Literal( - Number(1), - ), ArgumentName( Identifier( "order_by_custom", @@ -42,7 +28,7 @@ V1( ): Literal( Array [ Object { - "first_name": String("Desc_custom"), + "first_name": String("Asc_custom"), }, ], ), @@ -153,6 +139,91 @@ V1( }, }, ), + Alias( + Identifier( + "a1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "limit_custom", + ), + ): Literal( + Number(1), + ), + ArgumentName( + Identifier( + "offset_custom", + ), + ): Literal( + Number(1), + ), + ArgumentName( + Identifier( + "order_by_custom", + ), + ): Literal( + Array [ + Object { + "first_name": String("Desc_custom"), + }, + ], + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_model_v2_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_model_v2_admin.snap index 31762dfb307..a5ca6cd0627 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_model_v2_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by__with_model_v2_admin.snap @@ -28,19 +28,15 @@ V1( ): Literal( Array [ Object { - "first_name": String("Desc"), + "first_name": String("Asc"), }, ], ), }, filter: None, order_by: [], - limit: Some( - 1, - ), - offset: Some( - 1, - ), + limit: None, + offset: None, }, selection: { Alias( @@ -141,6 +137,81 @@ V1( }, }, ), + Alias( + Identifier( + "a1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "order_by", + ), + ): Literal( + Array [ + Object { + "first_name": String("Desc"), + }, + ], + ), + }, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by_admin.snap index 31762dfb307..a5ca6cd0627 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__order_by_admin.snap @@ -28,19 +28,15 @@ V1( ): Literal( Array [ Object { - "first_name": String("Desc"), + "first_name": String("Asc"), }, ], ), }, filter: None, order_by: [], - limit: Some( - 1, - ), - offset: Some( - 1, - ), + limit: None, + offset: None, }, selection: { Alias( @@ -141,6 +137,81 @@ V1( }, }, ), + Alias( + Identifier( + "a1", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "order_by", + ), + ): Literal( + Array [ + Object { + "first_name": String("Desc"), + }, + ], + ), + }, + filter: None, + order_by: [], + limit: Some( + 1, + ), + offset: Some( + 1, + ), + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__filter_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__filter_user_1.snap index dc9c4ed840c..267d03b25e2 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__filter_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__filter_user_1.snap @@ -7,7 +7,7 @@ V1( queries: { Alias( Identifier( - "ArticlesByAuthorMany", + "article_author_match", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_admin.snap index 26384835213..55086434308 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_admin.snap @@ -7,7 +7,76 @@ V1( queries: { Alias( Identifier( - "ArticlesByAuthorMany", + "article_author_match", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ArticlesByAuthor", + ), + ), + arguments: { + ArgumentName( + Identifier( + "args_custom", + ), + ): Literal( + Object { + "author_id": Number(2), + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "article_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "article_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "title", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "title", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "article_author_mismatch", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_user.snap index 26384835213..55086434308 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args__with_graphql_config_user.snap @@ -7,7 +7,76 @@ V1( queries: { Alias( Identifier( - "ArticlesByAuthorMany", + "article_author_match", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ArticlesByAuthor", + ), + ), + arguments: { + ArgumentName( + Identifier( + "args_custom", + ), + ): Literal( + Object { + "author_id": Number(2), + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "article_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "article_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "title", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "title", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "article_author_mismatch", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args_admin.snap index 1cc4c9a46b7..17a06adbbe9 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__select_with_args_admin.snap @@ -7,7 +7,74 @@ V1( queries: { Alias( Identifier( - "ArticlesByAuthorMany", + "article_author_match", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "ArticlesByAuthor", + ), + ), + arguments: { + ArgumentName( + Identifier( + "author_id", + ), + ): Literal( + Number(2), + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "article_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "article_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "title", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "title", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "article_author_mismatch", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_admin.snap index c0f48fdaa49..206f76a6488 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_admin.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { @@ -105,7 +328,230 @@ V1( ), Alias( Identifier( - "AuthorTwoMany", + "AuthorTwoManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_user.snap index c0f48fdaa49..206f76a6488 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__boolean_expression_type_user.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { @@ -105,7 +328,230 @@ V1( ), Alias( Identifier( - "AuthorTwoMany", + "AuthorTwoManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_admin.snap index 034d147ae16..29cc61435d5 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_admin.snap @@ -7,7 +7,273 @@ V1( queries: { Alias( Identifier( - "Track", + "TrackNameEq", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_eq": Number(1), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGtAndLt", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gt": Number(1), + "_lt": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLte", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gte": Number(1), + "_lte": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLteAndNeq", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_1.snap index 034d147ae16..29cc61435d5 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_1.snap @@ -7,7 +7,273 @@ V1( queries: { Alias( Identifier( - "Track", + "TrackNameEq", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_eq": Number(1), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGtAndLt", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gt": Number(1), + "_lt": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLte", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gte": Number(1), + "_lte": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLteAndNeq", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_2.snap index 034d147ae16..29cc61435d5 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__ndc_operators_user_2.snap @@ -7,7 +7,273 @@ V1( queries: { Alias( Identifier( - "Track", + "TrackNameEq", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_eq": Number(1), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGtAndLt", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gt": Number(1), + "_lt": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLte", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Tracks", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "TrackId": Object { + "_gte": Number(1), + "_lte": Number(3), + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "AlbumId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "AlbumId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "Name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "Name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "TrackId", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "TrackId", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "TrackGteAndLteAndNeq", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__array_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__array_admin.snap index 7efdac0e488..a1721312530 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__array_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__array_admin.snap @@ -7,7 +7,80 @@ V1( queries: { Alias( Identifier( - "InstitutionMany", + "where_does_john_hughes_work", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "institutions", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "staff": Object { + "last_name": Object { + "_eq": String("Hughes"), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "location", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "location", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "where_has_staff_with_a_dog", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__object_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__object_admin.snap index 0e154691b84..d71eff6c0e0 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__object_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__nested_select__object_admin.snap @@ -7,7 +7,148 @@ V1( queries: { Alias( Identifier( - "InstitutionMany", + "match_london", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "institutions", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "location": Object { + "city": Object { + "_eq": String("London"), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "location", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "location", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "location_country", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "location", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "staff", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "staff", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "staff_first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "staff", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "departments", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "departments", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "match_uk", ), ): Model( ModelSelection { @@ -148,6 +289,164 @@ V1( }, }, ), + Alias( + Identifier( + "match_two_nested_fields", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "institutions", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "songs": Object { + "primaryAnthemTrackId": Object { + "_eq": Number(2270), + }, + "secondaryAnthemTrackId": Object { + "_eq": Number(2271), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "songs", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "songs", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "nested_logical_operators", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "institutions", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "songs": Object { + "_or": Array [ + Object { + "primaryAnthemTrackId": Object { + "_eq": Number(2270), + }, + }, + Object { + "primaryAnthemTrackId": Object { + "_eq": Number(3421), + }, + }, + ], + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "songs", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "songs", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), }, }, ) diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_admin.snap index c64176a5474..5f633d416a3 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_admin.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { @@ -105,7 +328,230 @@ V1( ), Alias( Identifier( - "AuthorTwoMany", + "AuthorTwoManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_user.snap index c64176a5474..5f633d416a3 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__shared_boolean_expression_user.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { @@ -105,7 +328,230 @@ V1( ), Alias( Identifier( - "AuthorTwoMany", + "AuthorTwoManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "AuthorsTwo", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorTwoManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_admin.snap index e754b7077d7..d567223d296 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_admin.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_user.snap index e754b7077d7..d567223d296 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__simple_user.snap @@ -7,7 +7,230 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_not": Object { + "first_name": Object { + "_is_null": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_or": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where", + ), + ): Literal( + Object { + "_and": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyNestedBool", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_admin.snap index 855b75756ba..7be1f1fb971 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_admin.snap @@ -7,7 +7,155 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where_custom", + ), + ): Literal( + Object { + "_not_custom": Object { + "first_name": Object { + "_is_null_custom": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where_custom", + ), + ): Literal( + Object { + "_or_custom": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_user.snap b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_user.snap index 855b75756ba..7be1f1fb971 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_user.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__ir_execute__models__select_many__where__with_graphql_config_user.snap @@ -7,7 +7,155 @@ V1( queries: { Alias( Identifier( - "AuthorMany", + "AuthorManyNot", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where_custom", + ), + ): Literal( + Object { + "_not_custom": Object { + "first_name": Object { + "_is_null_custom": Bool(true), + }, + }, + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyOr", + ), + ): Model( + ModelSelection { + target: ModelTarget { + subgraph: SubgraphName( + "default", + ), + model_name: ModelName( + Identifier( + "Authors", + ), + ), + arguments: { + ArgumentName( + Identifier( + "where_custom", + ), + ): Literal( + Object { + "_or_custom": Array [ + Object { + "first_name": Object { + "_like": String("Pet%"), + }, + }, + ], + }, + ), + }, + filter: None, + order_by: [], + limit: None, + offset: None, + }, + selection: { + Alias( + Identifier( + "author_id", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "author_id", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + Alias( + Identifier( + "first_name", + ), + ): Field( + ObjectFieldSelection { + target: ObjectFieldTarget { + field_name: FieldName( + Identifier( + "first_name", + ), + ), + arguments: {}, + }, + selection: None, + }, + ), + }, + }, + ), + Alias( + Identifier( + "AuthorManyAnd", ), ): Model( ModelSelection { diff --git a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_admin.snap b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_admin.snap index 315c807b29a..3e2fa189e3d 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_admin.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_admin.snap @@ -5,7 +5,16 @@ expression: rowsets { "Ok": [ { - "rows": [] + "rows": [ + { + "author_id": 1, + "first_name": "Peter" + }, + { + "author_id": 2, + "first_name": "John" + } + ] } ] } diff --git a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_1.snap b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_1.snap index 315c807b29a..b7fcb4cdbc4 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_1.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_1.snap @@ -5,7 +5,12 @@ expression: rowsets { "Ok": [ { - "rows": [] + "rows": [ + { + "author_id": 1, + "first_name": "Peter" + } + ] } ] } diff --git a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_2.snap b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_2.snap index 315c807b29a..4864726c78c 100644 --- a/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_2.snap +++ b/v3/crates/engine/tests/snapshots/execution__common__rowsets_execute__models__select_many__limit_offset__offset_user_2.snap @@ -5,7 +5,12 @@ expression: rowsets { "Ok": [ { - "rows": [] + "rows": [ + { + "author_id": 2, + "first_name": "John" + } + ] } ] } diff --git a/v3/crates/execute/src/query_usage.rs b/v3/crates/execute/src/query_usage.rs index a2566e579b7..e96b1cd8a82 100644 --- a/v3/crates/execute/src/query_usage.rs +++ b/v3/crates/execute/src/query_usage.rs @@ -135,9 +135,9 @@ fn analyze_input_annotation(annotation: &graphql_schema::InputAnnotation) -> Vec })); } graphql_schema::InputAnnotation::BooleanExpression( - graphql_schema::BooleanExpressionAnnotation::BooleanExpressionArgument { field }, + graphql_schema::BooleanExpressionAnnotation::ObjectBooleanExpressionField(field), ) => match field { - graphql_schema::ModelFilterArgument::Field { + graphql_schema::ObjectBooleanExpressionField::Field { field_name, object_type, deprecated, @@ -154,7 +154,9 @@ fn analyze_input_annotation(annotation: &graphql_schema::InputAnnotation) -> Vec deprecated_reason: reason, })); } - graphql_schema::ModelFilterArgument::RelationshipField(relationship_annotation) => { + graphql_schema::ObjectBooleanExpressionField::RelationshipField( + relationship_annotation, + ) => { let DeprecatedDetails { is_deprecated, reason, @@ -172,12 +174,13 @@ fn analyze_input_annotation(annotation: &graphql_schema::InputAnnotation) -> Vec deprecated_reason: reason, })); } - graphql_schema::ModelFilterArgument::AndOp - | graphql_schema::ModelFilterArgument::OrOp - | graphql_schema::ModelFilterArgument::NotOp => {} + graphql_schema::ObjectBooleanExpressionField::AndOp + | graphql_schema::ObjectBooleanExpressionField::OrOp + | graphql_schema::ObjectBooleanExpressionField::NotOp => {} }, graphql_schema::InputAnnotation::BooleanExpression( - graphql_schema::BooleanExpressionAnnotation::BooleanExpression, + graphql_schema::BooleanExpressionAnnotation::BooleanExpressionRootField + | graphql_schema::BooleanExpressionAnnotation::ScalarBooleanExpressionField(_), ) | graphql_schema::InputAnnotation::CommandArgument { .. } | graphql_schema::InputAnnotation::Relay(_) @@ -231,8 +234,6 @@ fn analyze_model_input_annotation( } graphql_schema::ModelInputAnnotation::ModelArgumentsExpression | graphql_schema::ModelInputAnnotation::ModelArgument { .. } - | graphql_schema::ModelInputAnnotation::ComparisonOperation { .. } - | graphql_schema::ModelInputAnnotation::IsNullOperation | graphql_schema::ModelInputAnnotation::ModelOrderByExpression | graphql_schema::ModelInputAnnotation::ModelOrderByNestedExpression { .. } | graphql_schema::ModelInputAnnotation::ModelOrderByDirection { .. } diff --git a/v3/crates/graphql/frontend/src/to_opendd_ir.rs b/v3/crates/graphql/frontend/src/to_opendd_ir.rs index 07f9e3d064f..942f2cf040c 100644 --- a/v3/crates/graphql/frontend/src/to_opendd_ir.rs +++ b/v3/crates/graphql/frontend/src/to_opendd_ir.rs @@ -16,7 +16,7 @@ pub fn to_opendd_ir(operation: &Operation) -> QueryRequest { } let mut queries = IndexMap::new(); - for query in operation.selection_set.fields.values() { + for (query_alias, query) in &operation.selection_set.fields { for field_call in query.field_calls.values() { match field_call.info.generic { graphql_schema::Annotation::Output( @@ -29,8 +29,7 @@ pub fn to_opendd_ir(operation: &Operation) -> QueryRequest { }, ), ) => { - let opendd_alias = - Alias::new(Identifier::new(field_call.name.as_str()).unwrap()); + let opendd_alias = Alias::new(Identifier::new(query_alias.0.as_str()).unwrap()); let selection = to_model_selection(&query.selection_set.fields); @@ -60,8 +59,7 @@ pub fn to_opendd_ir(operation: &Operation) -> QueryRequest { graphql_schema::RootFieldAnnotation::FunctionCommand { name, .. }, ), ) => { - let opendd_alias = - Alias::new(Identifier::new(field_call.name.as_str()).unwrap()); + let opendd_alias = Alias::new(Identifier::new(query_alias.0.as_str()).unwrap()); let selection = to_model_selection(&query.selection_set.fields); @@ -102,8 +100,8 @@ fn to_model_arguments( let mut model_limit = None; for (name, argument) in arguments { - // currently we are magically matching on strings, really we should be looking up the correct names - // for each thing in `graphql_config` in resolved metadata + // currently we are magically matching on strings, really we should be using the graphql field annotations + // to determine the role of each field match name.as_str() { "offset" => { if let Some(offset_value) = match &argument.value { diff --git a/v3/crates/graphql/ir/src/filter.rs b/v3/crates/graphql/ir/src/filter.rs index 25efc8a19b0..1885aa6e6a4 100644 --- a/v3/crates/graphql/ir/src/filter.rs +++ b/v3/crates/graphql/ir/src/filter.rs @@ -5,15 +5,17 @@ use metadata_resolve::{DataConnectorLink, FieldMapping, Qualified}; use open_dds::models::ModelName; use open_dds::relationships::{RelationshipName, RelationshipType}; use serde::Serialize; +use std::borrow::Cow; use std::collections::BTreeMap; +use std::ops::Deref; use crate::error; use crate::model_tracking::count_model; -use graphql_schema::FilterRelationshipAnnotation; use graphql_schema::GDS; use graphql_schema::{self}; +use graphql_schema::{BooleanExpressionAnnotation, InputAnnotation, ObjectFieldKind}; use graphql_schema::{ - BooleanExpressionAnnotation, InputAnnotation, ModelInputAnnotation, ObjectFieldKind, + FilterRelationshipAnnotation, ObjectBooleanExpressionField, ScalarBooleanExpressionField, }; use open_dds::{ data_connector::{DataConnectorColumnName, DataConnectorOperatorName}, @@ -52,125 +54,137 @@ pub fn resolve_filter_expression<'s>( type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, usage_counts: &mut UsagesCounts, ) -> Result, error::Error> { - let mut expressions = Vec::new(); - for field in fields.values() { - let field_filter_expression = - build_filter_expression(field, data_connector_link, type_mappings, usage_counts)?; - expressions.push(field_filter_expression); - } - Ok(Expression::mk_and(expressions)) -} - -fn build_filter_expression<'s>( - field: &normalized_ast::InputField<'s, GDS>, - data_connector_link: &'s DataConnectorLink, - type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, - usage_counts: &mut UsagesCounts, -) -> Result, error::Error> { - let boolean_expression_annotation = get_boolean_expression_annotation(field.info.generic)?; - build_filter_expression_from_boolean_expression( - boolean_expression_annotation, - field, + resolve_object_boolean_expression( + fields, data_connector_link, type_mappings, - &mut vec![], + &[], usage_counts, ) } -fn build_filter_expression_from_boolean_expression<'s>( - boolean_expression_annotation: &'s BooleanExpressionAnnotation, - field: &normalized_ast::InputField<'s, GDS>, +fn resolve_object_boolean_expression<'s>( + fields: &IndexMap>, data_connector_link: &'s DataConnectorLink, type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, - field_path: &mut Vec, + column_path: &[&'s DataConnectorColumnName], usage_counts: &mut UsagesCounts, ) -> Result, error::Error> { - match boolean_expression_annotation { - // "_and" - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: graphql_schema::ModelFilterArgument::AndOp, - } => { - // The "_and" field value should be a list - let and_values = field.value.as_list()?; + let field_expressions = fields + .values() + .map(|field| { + let field_annotation = + extract_object_boolean_expression_field_annotation(field.info.generic)?; - let and_expressions = and_values - .iter() - .map(|value| { - let value_object = value.as_object()?; - resolve_filter_object( - value_object, + let field_expression = match field_annotation { + // "_and" field + ObjectBooleanExpressionField::AndOp => { + // The "_and" field value should be a list + let and_values = field.value.as_list()?; + + let and_expressions = and_values + .iter() + .map(|value| { + let value_object = value.as_object()?; + resolve_object_boolean_expression( + value_object, + data_connector_link, + type_mappings, + column_path, + usage_counts, + ) + }) + .collect::, _>>()?; + + Expression::mk_and(and_expressions) + } + // "_or" field + ObjectBooleanExpressionField::OrOp => { + // The "_or" field value should be a list + let or_values = field.value.as_list()?; + + let or_expressions = or_values + .iter() + .map(|value| { + let value_object = value.as_object()?; + resolve_object_boolean_expression( + value_object, + data_connector_link, + type_mappings, + column_path, + usage_counts, + ) + }) + .collect::, _>>()?; + + Expression::mk_or(or_expressions) + } + // "_not" field + ObjectBooleanExpressionField::NotOp => { + // The "_not" field value should be an object + let not_value = field.value.as_object()?; + + let not_filter_expression = resolve_object_boolean_expression( + not_value, data_connector_link, type_mappings, + column_path, usage_counts, - ) - }) - .collect::, _>>()?; - - Ok(Expression::mk_and(and_expressions)) - } - // "_or" - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: graphql_schema::ModelFilterArgument::OrOp, - } => { - // The "_or" field value should be a list - let or_values = field.value.as_list()?; - - let or_expressions = or_values - .iter() - .map(|value| { - let value_object = value.as_object()?; - resolve_filter_object( - value_object, - data_connector_link, - type_mappings, - usage_counts, - ) - }) - .collect::, _>>()?; - - Ok(Expression::mk_or(or_expressions)) - } - // "_not" - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: graphql_schema::ModelFilterArgument::NotOp, - } => { - // The "_not" field value should be an object - let not_value = field.value.as_object()?; - - let not_filter_expression = - resolve_filter_object(not_value, data_connector_link, type_mappings, usage_counts)?; - Ok(Expression::mk_not(not_filter_expression)) - } - // The column that we want to use for filtering. - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: - graphql_schema::ModelFilterArgument::Field { + )?; + Expression::mk_not(not_filter_expression) + } + // comparableField field + ObjectBooleanExpressionField::Field { field_name, object_type, object_field_kind, - .. - }, - } => { - let FieldMapping { column, .. } = - get_field_mapping_of_field_name(type_mappings, object_type, field_name)?; + deprecated: _, + } => { + let FieldMapping { column, .. } = + get_field_mapping_of_field_name(type_mappings, object_type, field_name)?; - let inner_is_array_field = matches!(object_field_kind, ObjectFieldKind::Array); + let field_value = field.value.as_object()?; - build_comparison_expression( - field, - field_path, - inner_is_array_field, - &column, - data_connector_link, - type_mappings, - ) - } - // Relationship field used for filtering. - // This relationship can either point to another relationship or a column. - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: - graphql_schema::ModelFilterArgument::RelationshipField(FilterRelationshipAnnotation { + match object_field_kind { + ObjectFieldKind::Object => { + // Append the current column to the column_path before descending into the nested object expression + let field_path = column_path + .iter() + .copied() + .chain([column]) + .collect::>(); + resolve_object_boolean_expression( + field_value, + data_connector_link, + type_mappings, + &field_path, + usage_counts, + )? + } + ObjectFieldKind::ObjectArray => { + let inner_expression = resolve_object_boolean_expression( + field_value, + data_connector_link, + type_mappings, + &[], // Reset the column path because we're nesting the expression inside an exists that itself captures the field path + usage_counts, + )?; + + Expression::LocalNestedArray { + column: column.clone(), + field_path: column_path.iter().copied().cloned().collect(), + predicate: Box::new(inner_expression), + } + } + ObjectFieldKind::Scalar => resolve_scalar_boolean_expression( + field_value, + data_connector_link, + column_path, + column, + )?, + } + } + ObjectBooleanExpressionField::RelationshipField(FilterRelationshipAnnotation { relationship_name, relationship_type, source_type, @@ -179,49 +193,115 @@ fn build_filter_expression_from_boolean_expression<'s>( target_model_name, mappings, deprecated: _, - }), - } => { - // Add the target model being used in the usage counts - count_model(target_model_name, usage_counts); + }) => { + // Add the target model being used in the usage counts + count_model(target_model_name, usage_counts); - // This map contains the relationships or the columns of the - // relationship that needs to be used for ordering. - let filter_object = field.value.as_object()?; + // This map contains the relationships or the columns of the + // relationship that needs to be used for ordering. + let filter_object = field.value.as_object()?; - let mut expressions = Vec::new(); + let relationship_predicate = resolve_object_boolean_expression( + filter_object, + &target_source.model.data_connector, + &target_source.model.type_mappings, + &[], // We're traversing across the relationship, so we reset the field path + usage_counts, + )?; - for field in filter_object.values() { - let field_filter_expression = build_filter_expression( - field, - &target_source.model.data_connector, - &target_source.model.type_mappings, - usage_counts, - )?; - expressions.push(field_filter_expression); - } + // build and return relationshp comparison expression + build_relationship_comparison_expression( + type_mappings, + column_path, + data_connector_link, + relationship_name, + relationship_type, + source_type, + target_model_name, + target_source, + target_type, + mappings, + relationship_predicate, + )? + } + }; - let relationship_predicate = Expression::mk_and(expressions); + Ok(field_expression) + }) + .collect::, error::Error>>()?; - // build and return relationshp comparison expression - build_relationship_comparison_expression( - type_mappings, - field_path, - data_connector_link, - relationship_name, - relationship_type, - source_type, - target_model_name, - target_source, - target_type, - mappings, - relationship_predicate, - ) + Ok(Expression::mk_and(field_expressions)) +} + +fn extract_object_boolean_expression_field_annotation( + annotation: &graphql_schema::Annotation, +) -> Result<&ObjectBooleanExpressionField, error::Error> { + match annotation { + graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + object_boolean_expression_field, + ), + )) => Ok(object_boolean_expression_field), + _ => Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: annotation.clone(), } - other_boolean_annotation => Err(error::InternalEngineError::UnexpectedAnnotation { - annotation: graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( - other_boolean_annotation.clone(), - )), - })?, + .into()), + } +} + +fn resolve_scalar_boolean_expression<'s>( + fields: &IndexMap>, + data_connector_link: &'s DataConnectorLink, + column_path: &[&'s DataConnectorColumnName], + column: &DataConnectorColumnName, +) -> Result, error::Error> { + let field_expressions = fields + .values() + .map(|field| { + let field_annotation = + extract_scalar_boolean_expression_field_annotation(field.info.generic)?; + + let field_expression = match field_annotation { + ScalarBooleanExpressionField::IsNullOperation => { + build_is_null_expression(column_path, column, &field.value)? + } + ScalarBooleanExpressionField::ComparisonOperation { operator_mapping } => { + let operator = + operator_mapping + .get(&data_connector_link.name) + .ok_or_else(|| { + error::InternalEngineError::OperatorMappingError( + error::OperatorMappingError::MissingEntryForDataConnector { + column_name: column.clone(), + data_connector_name: data_connector_link.name.clone(), + }, + ) + })?; + + build_binary_comparison_expression(operator, column_path, column, &field.value) + } + }; + + Ok(field_expression) + }) + .collect::, error::Error>>()?; + + Ok(Expression::mk_and(field_expressions)) +} + +fn extract_scalar_boolean_expression_field_annotation( + annotation: &graphql_schema::Annotation, +) -> Result<&ScalarBooleanExpressionField, error::Error> { + match annotation { + graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( + BooleanExpressionAnnotation::ScalarBooleanExpressionField( + scalar_boolean_expression_field, + ), + )) => Ok(scalar_boolean_expression_field), + _ => Err(error::InternalEngineError::UnexpectedAnnotation { + annotation: annotation.clone(), + } + .into()), } } @@ -265,7 +345,7 @@ fn get_relationship_predicate_execution_strategy( /// and passed as `relationship_predicate`. pub(crate) fn build_relationship_comparison_expression<'s>( type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, - field_path: &[DataConnectorColumnName], + column_path: &[&'s DataConnectorColumnName], data_connector_link: &'s DataConnectorLink, relationship_name: &'s RelationshipName, relationship_type: &'s RelationshipType, @@ -276,7 +356,16 @@ pub(crate) fn build_relationship_comparison_expression<'s>( mappings: &'s Vec, relationship_predicate: Expression<'s>, ) -> Result, error::Error> { - // Determinde whether the relationship is local or remote + if !column_path.is_empty() { + return Err( + error::InternalDeveloperError::NestedObjectRelationshipInPredicate { + relationship_name: relationship_name.clone(), + } + .into(), + ); + } + + // Determine whether the relationship is local or remote match get_relationship_predicate_execution_strategy( data_connector_link, &target_source.model.data_connector, @@ -317,7 +406,8 @@ pub(crate) fn build_relationship_comparison_expression<'s>( } = get_field_mapping_of_field_name(type_mappings, source_type, source_field)?; let equal_operators = comparison_operators - .map(|ops| ops.equality_operators) + .as_ref() + .map(|ops| Cow::Borrowed(&ops.equality_operators)) .unwrap_or_default(); let eq_operator = equal_operators.first().ok_or_else(|| { @@ -331,8 +421,8 @@ pub(crate) fn build_relationship_comparison_expression<'s>( })?; let source_ndc_column = SourceNdcColumn { - column: source_column, - field_path: field_path.to_owned(), + column: source_column.clone(), + field_path: column_path.iter().copied().cloned().collect(), eq_operator: eq_operator.clone(), }; @@ -361,144 +451,26 @@ pub(crate) fn build_relationship_comparison_expression<'s>( } } -fn build_comparison_expression<'s>( - field: &normalized_ast::InputField<'s, GDS>, - field_path: &mut Vec, - is_array_field: bool, - column: &DataConnectorColumnName, - data_connector_link: &'s DataConnectorLink, - type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, -) -> Result, error::Error> { - let mut expressions = Vec::new(); - - for (_op_name, op_value) in field.value.as_object()? { - match op_value.info.generic { - graphql_schema::Annotation::Input(InputAnnotation::Model( - ModelInputAnnotation::IsNullOperation, - )) => { - let expression = - build_is_null_expression(column, &op_value.value, field_path.clone())?; - expressions.push(expression); - } - graphql_schema::Annotation::Input(InputAnnotation::Model( - ModelInputAnnotation::ComparisonOperation { operator_mapping }, - )) => { - let operator = - operator_mapping - .get(&data_connector_link.name) - .ok_or_else(|| { - error::InternalEngineError::OperatorMappingError( - error::OperatorMappingError::MissingEntryForDataConnector { - column_name: column.clone(), - data_connector_name: data_connector_link.name.clone(), - }, - ) - })?; - - let expression = build_binary_comparison_expression( - operator, - column, - &op_value.value, - field_path, - ); - expressions.push(expression); - } - // Nested field comparison - graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: - graphql_schema::ModelFilterArgument::Field { - field_name: inner_field_name, - object_field_kind, - object_type: inner_object_type, - .. - }, - }, - )) => { - // get correct inner column name - let FieldMapping { - column: inner_column, - .. - } = get_field_mapping_of_field_name( - type_mappings, - inner_object_type, - inner_field_name, - )?; - - let inner_is_array_field = matches!(object_field_kind, ObjectFieldKind::Array); - - if is_array_field { - // if we're matching in an array field, we look for the inner column directly - let inner_expression = build_comparison_expression( - op_value, - field_path, - inner_is_array_field, - &inner_column, - data_connector_link, - type_mappings, - )?; - - // and then wrap it in an `exists` - let exists_wrapper = Expression::LocalNestedArray { - column: column.clone(), - field_path: field_path.clone(), - predicate: Box::new(inner_expression), - }; - - expressions.push(exists_wrapper); - } else { - // otherwise we add to the field path - field_path.push(inner_column.clone()); - - // and look for the main column inside - let inner_expression = build_comparison_expression( - op_value, - field_path, - inner_is_array_field, - column, - data_connector_link, - type_mappings, - )?; - - expressions.push(inner_expression); - } - } - // Nested relationship comparison - graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: - graphql_schema::ModelFilterArgument::RelationshipField( - FilterRelationshipAnnotation { - relationship_name, .. - }, - ), - }, - )) => Err( - error::InternalDeveloperError::NestedObjectRelationshipInPredicate { - relationship_name: relationship_name.clone(), - }, - )?, - - annotation => Err(error::InternalEngineError::UnexpectedAnnotation { - annotation: annotation.clone(), - })?, - } - } - Ok(Expression::mk_and(expressions)) -} - /// Resolve `_is_null` GraphQL boolean operator fn build_is_null_expression<'s>( + column_path: &[&DataConnectorColumnName], column: &DataConnectorColumnName, value: &normalized_ast::Value<'s, GDS>, - field_path: Vec, ) -> Result, error::Error> { // Build an 'IsNull' unary comparison expression let unary_comparison_expression = Expression::LocalField(LocalFieldComparison::UnaryComparison { column: ComparisonTarget::Column { - name: column.clone(), - field_path, + // The column name is the root column + name: column_path.first().map_or(column, Deref::deref).clone(), + // The field path is the nesting path inside the root column, if any + field_path: column_path + .iter() + .copied() + .chain([column]) + .skip(1) + .cloned() + .collect(), }, operator: metadata_resolve::UnaryComparisonOperator::IsNull, }); @@ -516,14 +488,22 @@ fn build_is_null_expression<'s>( /// Generate a binary comparison operator fn build_binary_comparison_expression<'s>( operator: &DataConnectorOperatorName, + column_path: &[&DataConnectorColumnName], column: &DataConnectorColumnName, value: &normalized_ast::Value<'s, GDS>, - field_path: &[DataConnectorColumnName], ) -> Expression<'s> { Expression::LocalField(LocalFieldComparison::BinaryComparison { column: ComparisonTarget::Column { - name: column.clone(), - field_path: field_path.to_vec(), + // The column name is the root column + name: column_path.first().map_or(column, Deref::deref).clone(), + // The field path is the nesting path inside the root column, if any + field_path: column_path + .iter() + .copied() + .chain([column]) + .skip(1) + .cloned() + .collect(), }, operator: operator.clone(), value: ComparisonValue::Scalar { @@ -532,45 +512,12 @@ fn build_binary_comparison_expression<'s>( }) } -fn resolve_filter_object<'s>( - fields: &IndexMap>, - data_connector_link: &'s DataConnectorLink, - type_mappings: &'s BTreeMap, metadata_resolve::TypeMapping>, - usage_counts: &mut UsagesCounts, -) -> Result, error::Error> { - let mut expressions = Vec::new(); - - for field in fields.values() { - expressions.push(build_filter_expression( - field, - data_connector_link, - type_mappings, - usage_counts, - )?); - } - Ok(Expression::mk_and(expressions)) -} - -fn get_boolean_expression_annotation( - annotation: &graphql_schema::Annotation, -) -> Result<&BooleanExpressionAnnotation, error::Error> { - match annotation { - graphql_schema::Annotation::Input(InputAnnotation::BooleanExpression( - boolean_expression_annotation, - )) => Ok(boolean_expression_annotation), - _ => Err(error::InternalEngineError::UnexpectedAnnotation { - annotation: annotation.clone(), - } - .into()), - } -} - /// get column name for field name -fn get_field_mapping_of_field_name( - type_mappings: &BTreeMap, metadata_resolve::TypeMapping>, +fn get_field_mapping_of_field_name<'a>( + type_mappings: &'a BTreeMap, metadata_resolve::TypeMapping>, type_name: &Qualified, field_name: &FieldName, -) -> Result { +) -> Result<&'a metadata_resolve::FieldMapping, error::Error> { let type_mapping = type_mappings.get(type_name).ok_or_else(|| { error::InternalDeveloperError::TypeMappingNotFound { type_name: type_name.clone(), @@ -582,7 +529,6 @@ fn get_field_mapping_of_field_name( .ok_or_else(|| error::InternalDeveloperError::FieldMappingNotFound { type_name: type_name.clone(), field_name: field_name.clone(), - })? - .clone()), + })?), } } diff --git a/v3/crates/graphql/ir/src/model_selection.rs b/v3/crates/graphql/ir/src/model_selection.rs index 3dc58eff2a6..2c570551df9 100644 --- a/v3/crates/graphql/ir/src/model_selection.rs +++ b/v3/crates/graphql/ir/src/model_selection.rs @@ -339,7 +339,7 @@ fn read_filter_input_arguments<'s>( // Where argument Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpression, + BooleanExpressionAnnotation::BooleanExpressionRootField, )) => { if filter_clause.is_some() { return Err(error::InternalEngineError::UnexpectedAnnotation { diff --git a/v3/crates/graphql/ir/src/query_root/select_many.rs b/v3/crates/graphql/ir/src/query_root/select_many.rs index 993c734a89e..6dfe8e548c1 100644 --- a/v3/crates/graphql/ir/src/query_root/select_many.rs +++ b/v3/crates/graphql/ir/src/query_root/select_many.rs @@ -110,7 +110,7 @@ pub fn select_many_generate_ir<'n, 's>( }, Annotation::Input(graphql_schema::InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpression, + BooleanExpressionAnnotation::BooleanExpressionRootField, )) => { where_clause = Some(filter::resolve_filter_expression( argument.value.as_object()?, diff --git a/v3/crates/graphql/ir/src/relationship.rs b/v3/crates/graphql/ir/src/relationship.rs index 5503ed6157a..b7974b9c152 100644 --- a/v3/crates/graphql/ir/src/relationship.rs +++ b/v3/crates/graphql/ir/src/relationship.rs @@ -107,7 +107,7 @@ pub fn generate_model_relationship_ir<'s>( } } InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpression, + BooleanExpressionAnnotation::BooleanExpressionRootField, ) => { if let Some(model_source) = &relationship_annotation.target_source { where_clause = Some(filter::resolve_filter_expression( diff --git a/v3/crates/graphql/schema/src/boolean_expression.rs b/v3/crates/graphql/schema/src/boolean_expression.rs index b35993ae136..9bd36996ca2 100644 --- a/v3/crates/graphql/schema/src/boolean_expression.rs +++ b/v3/crates/graphql/schema/src/boolean_expression.rs @@ -1,6 +1,7 @@ use hasura_authn_core::Role; use lang_graphql::ast::common as ast; use lang_graphql::schema::{self as gql_schema}; +use open_dds::data_connector::DataConnectorName; use open_dds::types::Deprecated; use open_dds::{ relationships::RelationshipType, @@ -9,6 +10,7 @@ use open_dds::{ use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; +use super::types::input_type; use super::types::output_type::get_object_type_representation; use super::types::output_type::relationship::FilterRelationshipAnnotation; use super::types::{BooleanExpressionAnnotation, InputAnnotation, ObjectFieldKind, TypeId}; @@ -16,9 +18,10 @@ use metadata_resolve::{ mk_name, BooleanExpressionComparableRelationship, ComparisonExpressionInfo, GlobalGraphqlConfig, IncludeLogicalOperators, ModelExpressionType, ModelWithArgumentPresets, ObjectBooleanExpressionGraphqlConfig, ObjectBooleanExpressionType, - ObjectComparisonExpressionInfo, ObjectComparisonKind, ObjectTypeWithRelationships, Qualified, - RelationshipCapabilities, RelationshipField, RelationshipModelMapping, - ResolvedObjectBooleanExpressionType, ScalarBooleanExpressionGraphqlConfig, + ObjectComparisonExpressionInfo, ObjectComparisonKind, ObjectTypeWithRelationships, + OperatorMapping, Qualified, QualifiedTypeReference, RelationshipCapabilities, + RelationshipField, RelationshipModelMapping, ResolvedObjectBooleanExpressionType, + ScalarBooleanExpressionGraphqlConfig, }; use crate::mk_deprecation_status; @@ -86,9 +89,9 @@ fn build_builtin_operator_schema( not_field_name.clone(), None, types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::NotOp, - }, + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::NotOp, + ), )), ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::new( type_name.0.clone(), @@ -106,9 +109,9 @@ fn build_builtin_operator_schema( and_field_name.clone(), None, types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::AndOp, - }, + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::AndOp, + ), )), ast::TypeContainer::list_null(ast::TypeContainer::named_non_null( gql_schema::RegisteredTypeName::new(type_name.0.clone()), @@ -125,9 +128,9 @@ fn build_builtin_operator_schema( or_field_name.clone(), None, types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::OrOp, - }, + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::OrOp, + ), )), ast::TypeContainer::list_null(ast::TypeContainer::named_non_null( gql_schema::RegisteredTypeName::new(type_name.0.clone()), @@ -160,7 +163,7 @@ fn build_comparable_fields_schema( .map_err(metadata_resolve::WithContext::from)?; if let Some(scalar_boolean_expression_graphql) = scalar_fields_graphql.get(field_name) { - let registered_type_name = get_scalar_comparison_input_type( + let registered_type_name = get_scalar_boolean_expression_type( builder, comparison_expression, scalar_boolean_expression_graphql, @@ -179,14 +182,14 @@ fn build_comparable_fields_schema( // create Field annotation for this field let annotation = types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::Field { + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::Field { field_name: field_name.clone(), object_field_kind: ObjectFieldKind::Scalar, object_type: object_type_name.clone(), deprecated: field_definition.deprecated.clone(), }, - }, + ), )); // calculate permissions @@ -253,17 +256,17 @@ fn build_comparable_fields_schema( // create Field annotation for field let annotation = types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::Field { + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::Field { field_name: field_name.clone(), object_field_kind: match object_comparison_expression.field_kind { ObjectComparisonKind::Object => ObjectFieldKind::Object, - ObjectComparisonKind::Array => ObjectFieldKind::Array, + ObjectComparisonKind::ObjectArray => ObjectFieldKind::ObjectArray, }, object_type: object_type_name.clone(), deprecated: field_definition.deprecated.clone(), }, - }, + ), )); // calculate permissions @@ -564,9 +567,9 @@ fn build_model_relationship_schema( relationship.field_name.clone(), None, types::Annotation::Input(InputAnnotation::BooleanExpression( - BooleanExpressionAnnotation::BooleanExpressionArgument { - field: types::ModelFilterArgument::RelationshipField(annotation), - }, + BooleanExpressionAnnotation::ObjectBooleanExpressionField( + types::ObjectBooleanExpressionField::RelationshipField(annotation), + ), )), ast::TypeContainer::named_null(gql_schema::RegisteredTypeName::new( target_filter_expression_graphql_type.0.clone(), @@ -691,7 +694,7 @@ fn build_schema_with_boolean_expression_type( } } -fn get_scalar_comparison_input_type( +fn get_scalar_boolean_expression_type( builder: &mut gql_schema::Builder, comparison_expression: &metadata_resolve::ComparisonExpressionInfo, scalar_boolean_expression_graphql: &metadata_resolve::ScalarBooleanExpressionGraphqlConfig, @@ -706,7 +709,7 @@ fn get_scalar_comparison_input_type( operators.push((op_name, input_type.clone())); } Ok( - builder.register_type(TypeId::ScalarTypeComparisonExpression { + builder.register_type(TypeId::InputScalarBooleanExpressionType { graphql_type_name, operators, operator_mapping: comparison_expression.operator_mapping.clone(), @@ -726,3 +729,90 @@ fn include_relationship_field( // Else, check for NDC capability || target_capabilities.is_some_and(|capabilities| capabilities.supports_relationships.as_ref().is_some_and(|r| r.supports_relation_comparisons)) } + +pub fn build_scalar_boolean_expression_input( + gds: &GDS, + builder: &mut gql_schema::Builder, + type_name: &ast::TypeName, + operators: &Vec<(ast::Name, QualifiedTypeReference)>, + operator_mapping: &BTreeMap, OperatorMapping>, + maybe_is_null_operator_name: &Option, +) -> Result, Error> { + let mut input_fields: BTreeMap< + ast::Name, + gql_schema::Namespaced>, + > = BTreeMap::new(); + + if let Some(is_null_operator_name) = maybe_is_null_operator_name { + // Add is_null field + let is_null_input_type = ast::TypeContainer { + base: ast::BaseTypeContainer::Named(gql_schema::RegisteredTypeName::boolean()), + nullable: true, + }; + + input_fields.insert( + is_null_operator_name.clone(), + builder.allow_all_namespaced(gql_schema::InputField::new( + is_null_operator_name.clone(), + None, + types::Annotation::Input(types::InputAnnotation::BooleanExpression( + types::BooleanExpressionAnnotation::ScalarBooleanExpressionField( + types::ScalarBooleanExpressionField::IsNullOperation, + ), + )), + is_null_input_type, + None, + gql_schema::DeprecationStatus::NotDeprecated, + )), + ); + } + + for (op_name, input_type) in operators { + // comparison_operator: input_type + let input_type = input_type::get_input_type(gds, builder, input_type)?; + // Presence of all scalar fields in the comparison expression is not compulsory. Users can filter rows based on + // scalar fields of their choice. Hence, the input type of each scalar field is nullable. + let nullable_input_type = ast::TypeContainer { + base: input_type.base, + nullable: true, + }; + + // this feels a bit loose, we're depending on the fact the ast::Name and + // OperatorName should be the same + let operator_name = open_dds::types::OperatorName::new(op_name.as_str().into()); + + // for each set of mappings, only return the mapping we actually need + // default to existing mapping where one is missing + let this_operator_mapping = operator_mapping + .iter() + .map(|(data_connector_name, mappings)| { + ( + data_connector_name.clone(), + mappings.get(&operator_name).clone(), + ) + }) + .collect(); + + input_fields.insert( + op_name.clone(), + builder.allow_all_namespaced(gql_schema::InputField::new( + op_name.clone(), + None, + types::Annotation::Input(types::InputAnnotation::BooleanExpression( + types::BooleanExpressionAnnotation::ScalarBooleanExpressionField( + types::ScalarBooleanExpressionField::ComparisonOperation { + operator_mapping: this_operator_mapping, + }, + ), + )), + nullable_input_type, + None, + gql_schema::DeprecationStatus::NotDeprecated, + )), + ); + } + + Ok(gql_schema::TypeInfo::InputObject( + gql_schema::InputObject::new(type_name.clone(), None, input_fields, Vec::new()), + )) +} diff --git a/v3/crates/graphql/schema/src/lib.rs b/v3/crates/graphql/schema/src/lib.rs index b2486cb2996..1454b4b2c87 100644 --- a/v3/crates/graphql/schema/src/lib.rs +++ b/v3/crates/graphql/schema/src/lib.rs @@ -40,9 +40,10 @@ pub use types::output_type::relationship::{ }; pub use types::{ Annotation, ApolloFederationRootFields, BooleanExpressionAnnotation, CommandSourceDetail, - EntityFieldTypeNameMapping, GlobalID, InputAnnotation, ModelFilterArgument, - ModelInputAnnotation, ModelOrderByDirection, NamespaceAnnotation, NodeFieldTypeNameMapping, - ObjectFieldKind, OutputAnnotation, RootFieldAnnotation, RootFieldKind, TypeKind, + EntityFieldTypeNameMapping, GlobalID, InputAnnotation, ModelInputAnnotation, + ModelOrderByDirection, NamespaceAnnotation, NodeFieldTypeNameMapping, + ObjectBooleanExpressionField, ObjectFieldKind, OutputAnnotation, RootFieldAnnotation, + RootFieldKind, ScalarBooleanExpressionField, TypeKind, }; /// This 'NamespacedGetter' looks up 'NamespacedNodeInfo's according to actual roles. @@ -190,12 +191,12 @@ impl gql_schema::SchemaContext for GDS { } => model_arguments::build_model_arguments_input_schema( self, builder, type_name, model_name, ), - types::TypeId::ScalarTypeComparisonExpression { + types::TypeId::InputScalarBooleanExpressionType { graphql_type_name, operators, operator_mapping, is_null_operator_name, - } => model_filter::build_scalar_comparison_input( + } => boolean_expression::build_scalar_boolean_expression_input( self, builder, graphql_type_name, diff --git a/v3/crates/graphql/schema/src/model_filter.rs b/v3/crates/graphql/schema/src/model_filter.rs index b8ce6106285..5afd0715c6e 100644 --- a/v3/crates/graphql/schema/src/model_filter.rs +++ b/v3/crates/graphql/schema/src/model_filter.rs @@ -1,16 +1,12 @@ -use super::types::input_type; use lang_graphql::ast::common as ast; -use lang_graphql::schema::{self as gql_schema, InputField, Namespaced}; -use open_dds::{data_connector::DataConnectorName, types::CustomTypeName}; -use std::collections::BTreeMap; +use lang_graphql::schema::{self as gql_schema}; +use open_dds::types::CustomTypeName; -use metadata_resolve::{OperatorMapping, Qualified, QualifiedTypeReference}; +use metadata_resolve::Qualified; use crate::types; use crate::GDS; -use crate::Error; - pub fn get_where_expression_input_field( builder: &mut gql_schema::Builder, gds_type_name: Qualified, @@ -23,7 +19,7 @@ pub fn get_where_expression_input_field( .clone(), None, types::Annotation::Input(types::InputAnnotation::BooleanExpression( - types::BooleanExpressionAnnotation::BooleanExpression, + types::BooleanExpressionAnnotation::BooleanExpressionRootField, )), ast::TypeContainer::named_null(builder.register_type( types::TypeId::InputObjectBooleanExpressionType { @@ -35,83 +31,3 @@ pub fn get_where_expression_input_field( gql_schema::DeprecationStatus::NotDeprecated, ) } - -pub fn build_scalar_comparison_input( - gds: &GDS, - builder: &mut gql_schema::Builder, - type_name: &ast::TypeName, - operators: &Vec<(ast::Name, QualifiedTypeReference)>, - operator_mapping: &BTreeMap, OperatorMapping>, - maybe_is_null_operator_name: &Option, -) -> Result, Error> { - let mut input_fields: BTreeMap>> = BTreeMap::new(); - - if let Some(is_null_operator_name) = maybe_is_null_operator_name { - // Add is_null field - let is_null_input_type = ast::TypeContainer { - base: ast::BaseTypeContainer::Named(gql_schema::RegisteredTypeName::boolean()), - nullable: true, - }; - - input_fields.insert( - is_null_operator_name.clone(), - builder.allow_all_namespaced(gql_schema::InputField::new( - is_null_operator_name.clone(), - None, - types::Annotation::Input(types::InputAnnotation::Model( - types::ModelInputAnnotation::IsNullOperation, - )), - is_null_input_type, - None, - gql_schema::DeprecationStatus::NotDeprecated, - )), - ); - } - - for (op_name, input_type) in operators { - // comparison_operator: input_type - let input_type = input_type::get_input_type(gds, builder, input_type)?; - // Presence of all scalar fields in the comparison expression is not compulsory. Users can filter rows based on - // scalar fields of their choice. Hence, the input type of each scalar field is nullable. - let nullable_input_type = ast::TypeContainer { - base: input_type.base, - nullable: true, - }; - - // this feels a bit loose, we're depending on the fact the ast::Name and - // OperatorName should be the same - let operator_name = open_dds::types::OperatorName::new(op_name.as_str().into()); - - // for each set of mappings, only return the mapping we actually need - // default to existing mapping where one is missing - let this_operator_mapping = operator_mapping - .iter() - .map(|(data_connector_name, mappings)| { - ( - data_connector_name.clone(), - mappings.get(&operator_name).clone(), - ) - }) - .collect(); - - input_fields.insert( - op_name.clone(), - builder.allow_all_namespaced(gql_schema::InputField::new( - op_name.clone(), - None, - types::Annotation::Input(types::InputAnnotation::Model( - types::ModelInputAnnotation::ComparisonOperation { - operator_mapping: this_operator_mapping, - }, - )), - nullable_input_type, - None, - gql_schema::DeprecationStatus::NotDeprecated, - )), - ); - } - - Ok(gql_schema::TypeInfo::InputObject( - gql_schema::InputObject::new(type_name.clone(), None, input_fields, Vec::new()), - )) -} diff --git a/v3/crates/graphql/schema/src/types.rs b/v3/crates/graphql/schema/src/types.rs index 3597b58e7d7..fd2fb0993b4 100644 --- a/v3/crates/graphql/schema/src/types.rs +++ b/v3/crates/graphql/schema/src/types.rs @@ -84,22 +84,7 @@ pub enum RootFieldKind { pub enum ObjectFieldKind { Scalar, Object, - Array, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub enum ModelFilterArgument { - AndOp, - OrOp, - NotOp, - Field { - field_name: types::FieldName, - object_type: Qualified, - object_field_kind: ObjectFieldKind, - /// To mark a field as deprecated in the field usage while reporting query usage analytics. - deprecated: Option, - }, - RelationshipField(FilterRelationshipAnnotation), + ObjectArray, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -230,14 +215,6 @@ pub enum ModelInputAnnotation { argument_kind: metadata_resolve::ArgumentKind, ndc_table_argument: Option, }, - ComparisonOperation { - #[serde( - serialize_with = "serialize_non_string_key_btreemap", - deserialize_with = "deserialize_non_string_key_btreemap" - )] - operator_mapping: BTreeMap, DataConnectorOperatorName>, - }, - IsNullOperation, ModelOrderByExpression, ModelOrderByNestedExpression { ndc_column: DataConnectorColumnName, @@ -268,8 +245,41 @@ pub enum ModelInputAnnotation { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] /// Annotations of the input types/fields related to boolean expressions. pub enum BooleanExpressionAnnotation { - BooleanExpression, - BooleanExpressionArgument { field: ModelFilterArgument }, + /// Marks the field that contains the root of the boolean expression. eg. a "where" field + BooleanExpressionRootField, + + /// Marks a field inside an object boolean expression + ObjectBooleanExpressionField(ObjectBooleanExpressionField), + + /// Marks a field inside an scalar boolean expression + ScalarBooleanExpressionField(ScalarBooleanExpressionField), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub enum ObjectBooleanExpressionField { + AndOp, + OrOp, + NotOp, + Field { + field_name: types::FieldName, + object_type: Qualified, + object_field_kind: ObjectFieldKind, + /// To mark a field as deprecated in the field usage while reporting query usage analytics. + deprecated: Option, + }, + RelationshipField(FilterRelationshipAnnotation), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub enum ScalarBooleanExpressionField { + ComparisonOperation { + #[serde( + serialize_with = "serialize_non_string_key_btreemap", + deserialize_with = "deserialize_non_string_key_btreemap" + )] + operator_mapping: BTreeMap, DataConnectorOperatorName>, + }, + IsNullOperation, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Display)] @@ -385,6 +395,12 @@ pub enum TypeId { gds_type_name: Qualified, graphql_type_name: ast::TypeName, }, + InputScalarBooleanExpressionType { + graphql_type_name: ast::TypeName, + operators: Vec<(ast::Name, QualifiedTypeReference)>, + operator_mapping: BTreeMap, OperatorMapping>, + is_null_operator_name: Option, + }, NodeRoot, ModelArgumentsInput { model_name: Qualified, @@ -395,12 +411,6 @@ pub enum TypeId { order_by_expression_identifier: Qualified, graphql_type_name: ast::TypeName, }, - ScalarTypeComparisonExpression { - graphql_type_name: ast::TypeName, - operators: Vec<(ast::Name, QualifiedTypeReference)>, - operator_mapping: BTreeMap, OperatorMapping>, - is_null_operator_name: Option, - }, OrderByEnumType { graphql_type_name: ast::TypeName, }, @@ -447,7 +457,7 @@ impl TypeId { | TypeId::InputObjectBooleanExpressionType { graphql_type_name, .. } - | TypeId::ScalarTypeComparisonExpression { + | TypeId::InputScalarBooleanExpressionType { graphql_type_name, .. } | TypeId::ModelOrderByExpression { diff --git a/v3/crates/metadata-resolve/src/helpers/boolean_expression.rs b/v3/crates/metadata-resolve/src/helpers/boolean_expression.rs index 3a69608cfae..4b1163757eb 100644 --- a/v3/crates/metadata-resolve/src/helpers/boolean_expression.rs +++ b/v3/crates/metadata-resolve/src/helpers/boolean_expression.rs @@ -66,7 +66,7 @@ pub(crate) fn validate_data_connector_with_object_boolean_expression_type( })); } } - boolean_expressions::ObjectComparisonKind::Array => { + boolean_expressions::ObjectComparisonKind::ObjectArray => { // raise a warning if our data connector does not support filtering nested arrays if !data_connector.capabilities.supports_nested_array_filtering { issues.push( diff --git a/v3/crates/metadata-resolve/src/stages/boolean_expressions/graphql.rs b/v3/crates/metadata-resolve/src/stages/boolean_expressions/graphql.rs index 6d14d648e05..857193b59b6 100644 --- a/v3/crates/metadata-resolve/src/stages/boolean_expressions/graphql.rs +++ b/v3/crates/metadata-resolve/src/stages/boolean_expressions/graphql.rs @@ -83,7 +83,7 @@ pub(crate) fn resolve_object_boolean_graphql( } } } - ComparableFieldKind::Object | ComparableFieldKind::Array => { + ComparableFieldKind::Object | ComparableFieldKind::ObjectArray => { // if this field isn't a scalar, let's see if it's an object instead let (_field_subgraph, raw_boolean_expression_type) = helpers::lookup_raw_boolean_expression( diff --git a/v3/crates/metadata-resolve/src/stages/boolean_expressions/object.rs b/v3/crates/metadata-resolve/src/stages/boolean_expressions/object.rs index 62f70217ddf..2a44b774583 100644 --- a/v3/crates/metadata-resolve/src/stages/boolean_expressions/object.rs +++ b/v3/crates/metadata-resolve/src/stages/boolean_expressions/object.rs @@ -269,7 +269,7 @@ fn resolve_comparable_fields( BooleanExpressionObjectAggregateOperand { r#type, .. }, ) => { let field_kind = match field.field_type.underlying_type { - QualifiedBaseType::List(_) => ComparableFieldKind::Array, + QualifiedBaseType::List(_) => ComparableFieldKind::ObjectArray, QualifiedBaseType::Named(_) => ComparableFieldKind::Object, }; (field_kind, TypeName::Custom(r#type.clone())) @@ -346,7 +346,7 @@ fn resolve_comparable_fields( }; } } - ComparableFieldKind::Object | ComparableFieldKind::Array => { + ComparableFieldKind::Object | ComparableFieldKind::ObjectArray => { // if this field isn't a scalar, let's see if it's an object instead let (field_subgraph, raw_boolean_expression_type) = helpers::lookup_raw_boolean_expression( @@ -362,7 +362,9 @@ fn resolve_comparable_fields( comparable_field_name.clone(), ObjectComparisonExpressionInfo { field_kind: match comparable_field_kind { - ComparableFieldKind::Array => ObjectComparisonKind::Array, + ComparableFieldKind::ObjectArray => { + ObjectComparisonKind::ObjectArray + } ComparableFieldKind::Object => ObjectComparisonKind::Object, ComparableFieldKind::Scalar => unreachable!(), }, diff --git a/v3/crates/metadata-resolve/src/stages/boolean_expressions/types.rs b/v3/crates/metadata-resolve/src/stages/boolean_expressions/types.rs index 60404bfebbf..a8801ae113d 100644 --- a/v3/crates/metadata-resolve/src/stages/boolean_expressions/types.rs +++ b/v3/crates/metadata-resolve/src/stages/boolean_expressions/types.rs @@ -73,7 +73,7 @@ pub struct BooleanExpressionsOutput { pub enum ComparableFieldKind { Scalar, Object, - Array, + ObjectArray, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -154,7 +154,7 @@ pub struct ComparisonExpressionInfo { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum ObjectComparisonKind { Object, - Array, + ObjectArray, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]