From 117d3e042bdf935d20a8f11bba55a5924b16d457 Mon Sep 17 00:00:00 2001 From: Raminder Singh Date: Wed, 21 Feb 2024 17:58:59 +0530 Subject: [PATCH 1/2] Revert "Merge pull request #472 from bryanmylee/master" This reverts commit eecd43e1c62b0ffc29015c435b2c00137324c956, reversing changes made to b6a3458592f2f78e765e7cb6cd545223a6402518. --- src/builder.rs | 171 ++++++------ src/parser_util.rs | 253 +++--------------- src/resolve.rs | 20 +- src/sql_types.rs | 3 +- test/expected/issue_237_field_merging.out | 72 ----- .../issue_237_field_merging_mismatched.out | 110 -------- test/sql/issue_237_field_merging.sql | 50 ---- .../issue_237_field_merging_mismatched.sql | 75 ------ 8 files changed, 117 insertions(+), 637 deletions(-) delete mode 100644 test/expected/issue_237_field_merging.out delete mode 100644 test/expected/issue_237_field_merging_mismatched.out delete mode 100644 test/sql/issue_237_field_merging.sql delete mode 100644 test/sql/issue_237_field_merging_mismatched.sql diff --git a/src/builder.rs b/src/builder.rs index 39e86ec2..3880550d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -51,7 +51,7 @@ fn read_argument<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { let input_value: __InputValue = match field.get_arg(arg_name) { Some(arg) => arg, @@ -81,7 +81,7 @@ fn read_argument_at_most<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { let at_most: gson::Value = read_argument( "atMost", @@ -161,7 +161,7 @@ fn read_argument_node_id<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { // nodeId is a base64 encoded string of [schema, table, pkey_val1, pkey_val2, ...] let node_id_base64_encoded_json_string: gson::Value = read_argument( @@ -182,7 +182,7 @@ fn read_argument_objects<'a, T>( variable_definitions: &Vec>, ) -> Result, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { // [{"name": "bob", "email": "a@b.com"}, {..}] let validated: gson::Value = read_argument( @@ -263,7 +263,7 @@ pub fn to_insert_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_name = type_ @@ -287,7 +287,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -295,12 +294,12 @@ where None => return Err("unknown field in insert".to_string()), Some(f) => builder_fields.push(match f.name().as_ref() { "affectedCount" => InsertSelection::AffectedCount { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "records" => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -309,7 +308,7 @@ where InsertSelection::Records(node_builder?) } "__typename" => InsertSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype .name() .expect("insert response type should have a name"), @@ -369,7 +368,7 @@ fn read_argument_set<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { let validated: gson::Value = read_argument("set", field, query_field, variables, variable_definitions)?; @@ -429,7 +428,7 @@ pub fn to_update_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_name = type_ @@ -457,7 +456,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -465,12 +463,12 @@ where None => return Err("unknown field in update".to_string()), Some(f) => builder_fields.push(match f.name().as_ref() { "affectedCount" => UpdateSelection::AffectedCount { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "records" => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -479,7 +477,7 @@ where UpdateSelection::Records(node_builder?) } "__typename" => UpdateSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype .name() .expect("update response type should have a name"), @@ -535,7 +533,7 @@ pub fn to_delete_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_name = type_ @@ -561,7 +559,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -569,12 +566,12 @@ where None => return Err("unknown field in delete".to_string()), Some(f) => builder_fields.push(match f.name().as_ref() { "affectedCount" => DeleteSelection::AffectedCount { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "records" => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -583,7 +580,7 @@ where DeleteSelection::Records(node_builder?) } "__typename" => DeleteSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype .name() .expect("delete response type should have a name"), @@ -645,7 +642,7 @@ pub fn to_function_call_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let alias = alias_or_name(query_field); @@ -722,7 +719,7 @@ fn read_func_call_args<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let inflected_to_sql_args = func_call_resp_type.inflected_to_sql_args(); let mut args = vec![]; @@ -1025,7 +1022,7 @@ fn restrict_allowed_arguments<'a, T>( query_field: &graphql_parser::query::Field<'a, T>, ) -> Result<(), String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let extra_keys: Vec<&str> = query_field .arguments @@ -1048,7 +1045,7 @@ fn read_argument_filter<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let validated: gson::Value = read_argument( "filter", @@ -1202,7 +1199,7 @@ fn read_argument_order_by<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { // [{"id": "DescNullsLast"}] let validated: gson::Value = read_argument( @@ -1298,7 +1295,7 @@ fn read_argument_cursor<'a, T>( variable_definitions: &Vec>, ) -> Result, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let validated: gson::Value = read_argument( arg_name, @@ -1339,7 +1336,7 @@ pub fn to_connection_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_ = type_.return_type(); @@ -1448,7 +1445,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -1457,24 +1453,24 @@ where Some(f) => builder_fields.push(match &f.type_.unmodified_type() { __Type::Edge(_) => ConnectionSelection::Edge(to_edge_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, )?), __Type::PageInfo(_) => ConnectionSelection::PageInfo(to_page_info_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, )?), _ => match f.name().as_ref() { "totalCount" => ConnectionSelection::TotalCount { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "__typename" => ConnectionSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype.name().expect("connection type should have a name"), }, _ => return Err("unexpected field type on connection".to_string()), @@ -1513,7 +1509,7 @@ fn to_page_info_builder<'a, T>( variables: &serde_json::Value, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_name = type_.name().ok_or(format!( @@ -1532,7 +1528,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -1540,19 +1535,19 @@ where None => return Err("unknown field in pageInfo".to_string()), Some(f) => builder_fields.push(match f.name().as_ref() { "startCursor" => PageInfoSelection::StartCursor { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "endCursor" => PageInfoSelection::EndCursor { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "hasPreviousPage" => PageInfoSelection::HasPreviousPage { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "hasNextPage" => PageInfoSelection::HasNextPage { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "__typename" => PageInfoSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype.name().expect("page info type should have a name"), }, _ => return Err("unexpected field type on pageInfo".to_string()), @@ -1576,7 +1571,7 @@ fn to_edge_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); let type_name = type_.name().ok_or(format!( @@ -1595,7 +1590,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -1605,7 +1599,7 @@ where __Type::Node(_) => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -1615,10 +1609,10 @@ where } _ => match f.name().as_ref() { "cursor" => EdgeSelection::Cursor { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), }, "__typename" => EdgeSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype.name().expect("edge type should have a name"), }, _ => return Err("unexpected field type on edge".to_string()), @@ -1644,7 +1638,7 @@ pub fn to_node_builder<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_().unmodified_type(); @@ -1716,7 +1710,6 @@ where fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -1729,7 +1722,7 @@ where )) } Some(f) => { - let alias = alias_or_name(&selection_field); + let alias = alias_or_name(selection_field); let node_selection = match &f.sql_type { Some(node_sql_type) => match node_sql_type { @@ -1744,7 +1737,7 @@ where __Type::Node(_) => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -1756,7 +1749,7 @@ where __Type::Connection(_) => { let connection_builder = to_connection_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], // TODO need ref to fkey here @@ -1784,14 +1777,14 @@ where }, _ => match f.name().as_ref() { "__typename" => NodeSelection::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: xtype.name().expect("node type should have a name"), }, _ => match f.type_().unmodified_type() { __Type::Connection(_) => { let con_builder = to_connection_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -1802,7 +1795,7 @@ where __Type::Node(_) => { let node_builder = to_node_builder( f, - &selection_field, + selection_field, fragment_definitions, variables, &[], @@ -1988,16 +1981,13 @@ impl __Schema { variables: &serde_json::Value, ) -> Result<__EnumValueBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let selection_fields = normalize_selection_set( &query_field.selection_set, fragment_definitions, &"__EnumValue".to_string(), variables, - &__Type::Query(QueryType { - schema: Arc::new(self.clone()), - }), )?; let mut builder_fields = vec![]; @@ -2011,7 +2001,7 @@ impl __Schema { "isDeprecated" => __EnumValueField::IsDeprecated, "deprecationReason" => __EnumValueField::DeprecationReason, "__typename" => __EnumValueField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: enum_value.name(), }, _ => { @@ -2023,7 +2013,7 @@ impl __Schema { }; builder_fields.push(__EnumValueSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: __enum_value_field, }); } @@ -2043,16 +2033,13 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result<__InputValueBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let selection_fields = normalize_selection_set( &query_field.selection_set, fragment_definitions, &"__InputValue".to_string(), variables, - &__Type::Query(QueryType { - schema: Arc::new(self.clone()), - }), )?; let mut builder_fields = vec![]; @@ -2068,7 +2055,7 @@ impl __Schema { let t_builder = self.to_type_builder_from_type( &t, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2079,7 +2066,7 @@ impl __Schema { "isDeprecated" => __InputValueField::IsDeprecated, "deprecationReason" => __InputValueField::DeprecationReason, "__typename" => __InputValueField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: input_value.name(), }, _ => { @@ -2091,7 +2078,7 @@ impl __Schema { }; builder_fields.push(__InputValueSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: __input_value_field, }); } @@ -2111,14 +2098,13 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result<__FieldBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let selection_fields = normalize_selection_set( &query_field.selection_set, fragment_definitions, &"__Field".to_string(), variables, - &field.type_, )?; let mut builder_fields = vec![]; @@ -2136,7 +2122,7 @@ impl __Schema { for arg in args { let f_builder = self.to_input_value_builder( &arg, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2150,7 +2136,7 @@ impl __Schema { let t_builder = self.to_type_builder_from_type( &t, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2160,14 +2146,14 @@ impl __Schema { "isDeprecated" => __FieldField::IsDeprecated, "deprecationReason" => __FieldField::DeprecationReason, "__typename" => __FieldField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: field.name(), }, _ => return Err(format!("unknown field in __Field {}", type_field_name)), }; builder_fields.push(__FieldSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: __field_field, }); } @@ -2188,7 +2174,7 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { if field.type_.unmodified_type() != __Type::__Type(__TypeType {}) { return Err("can not build query for non-__type type".to_string()); @@ -2240,7 +2226,7 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result<__TypeBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let field_map = field_map(&__Type::__Type(__TypeType {})); @@ -2249,9 +2235,6 @@ impl __Schema { fragment_definitions, &"__Type".to_string(), variables, - &__Type::Query(QueryType { - schema: Arc::new(self.clone()), - }), )?; let mut builder_fields = vec![]; @@ -2262,7 +2245,7 @@ impl __Schema { match field_map.get(type_field_name) { None => return Err(format!("unknown field on __Type: {}", type_field_name)), Some(f) => builder_fields.push(__TypeSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: match f.name().as_str() { "kind" => __TypeField::Kind, "name" => __TypeField::Name, @@ -2284,7 +2267,7 @@ impl __Schema { let f_builder = self.to_field_builder( &vec_field, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2305,7 +2288,7 @@ impl __Schema { for vec_field in vec_fields { let f_builder = self.to_input_value_builder( &vec_field, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2323,7 +2306,7 @@ impl __Schema { for interface in &interfaces { let interface_builder = self.to_type_builder_from_type( interface, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2345,7 +2328,7 @@ impl __Schema { for enum_value in &enum_values { let f_builder = self.to_enum_value_builder( enum_value, - &selection_field, + selection_field, fragment_definitions, variables, )?; @@ -2363,7 +2346,7 @@ impl __Schema { for ty in &types { let type_builder = self.to_type_builder_from_type( ty, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2387,7 +2370,7 @@ impl __Schema { let inner_type: __Type = (*(list_type.type_)).clone(); Some(self.to_type_builder_from_type( &inner_type, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2397,7 +2380,7 @@ impl __Schema { let inner_type = (*(non_null_type.type_)).clone(); Some(self.to_type_builder_from_type( &inner_type, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2408,7 +2391,7 @@ impl __Schema { __TypeField::OfType(unwrapped_type_builder) } "__typename" => __TypeField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: type_.name(), }, _ => { @@ -2437,16 +2420,13 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result<__DirectiveBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let selection_fields = normalize_selection_set( &query_field.selection_set, fragment_definitions, &__Directive::TYPE.to_string(), variables, - &__Type::Query(QueryType { - schema: Arc::new(self.clone()), - }), )?; let mut builder_fields = vec![]; @@ -2465,7 +2445,7 @@ impl __Schema { for arg in args { let builder = self.to_input_value_builder( arg, - &selection_field, + selection_field, fragment_definitions, variables, variable_definitions, @@ -2476,7 +2456,7 @@ impl __Schema { } "isRepeatable" => __DirectiveField::IsRepeatable, "__typename" => __DirectiveField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: __Directive::TYPE.to_string(), }, _ => { @@ -2489,7 +2469,7 @@ impl __Schema { }; builder_fields.push(__DirectiveSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: directive_field, }); } @@ -2509,7 +2489,7 @@ impl __Schema { variable_definitions: &Vec>, ) -> Result<__SchemaBuilder, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let type_ = field.type_.unmodified_type(); let type_name = type_ @@ -2526,7 +2506,6 @@ impl __Schema { fragment_definitions, &type_name, variables, - &field.type_, )?; for selection_field in selection_fields { @@ -2536,7 +2515,7 @@ impl __Schema { None => return Err(format!("unknown field in __Schema: {}", field_name)), Some(f) => { builder_fields.push(__SchemaSelection { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), selection: match f.name().as_str() { "types" => { let builders = self @@ -2607,7 +2586,7 @@ impl __Schema { __SchemaField::Directives(builders) } "__typename" => __SchemaField::Typename { - alias: alias_or_name(&selection_field), + alias: alias_or_name(selection_field), typename: field.name(), }, _ => { diff --git a/src/parser_util.rs b/src/parser_util.rs index b80ba970..046576da 100644 --- a/src/parser_util.rs +++ b/src/parser_util.rs @@ -1,7 +1,6 @@ -use crate::graphql::*; +use crate::graphql::{EnumSource, __InputValue, __Type, ___Type}; use crate::gson; use graphql_parser::query::*; -use graphql_parser::Pos; use std::collections::HashMap; pub fn alias_or_name<'a, T>(query_field: &graphql_parser::query::Field<'a, T>) -> String @@ -15,221 +14,34 @@ where .unwrap_or_else(|| query_field.name.as_ref().to_string()) } -pub fn merge_fields<'a, T, I>( - target_fields: &mut Vec>, - next_fields: I, - type_name: &str, - field_map: &HashMap, -) -> Result<(), String> -where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, - I: IntoIterator>, -{ - for field in next_fields { - merge_field(target_fields, field, type_name, field_map)? - } - Ok(()) -} - -pub fn merge_field<'a, T>( - target_fields: &mut Vec>, - mut field: Field<'a, T>, - type_name: &str, - field_map: &HashMap, -) -> Result<(), String> -where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, -{ - let Some((matching_idx, matching_field)) = target_fields - .iter() - .enumerate() - .find(|(_, target)| alias_or_name(target) == alias_or_name(&field)) - else { - target_fields.push(field); - return Ok(()); - }; - - can_fields_merge(matching_field, &field, type_name, field_map)?; - - field.position = field.position.min(matching_field.position); - - field.selection_set.span = - min_encapsulating_span(field.selection_set.span, matching_field.selection_set.span); - - // Subfields will be normalized and properly merged on a later pass. - field - .selection_set - .items - .extend(matching_field.selection_set.items.clone()); - - target_fields[matching_idx] = field; - - Ok(()) -} - -pub fn can_fields_merge<'a, T>( - field_a: &Field<'a, T>, - field_b: &Field<'a, T>, - type_name: &str, - field_map: &HashMap, -) -> Result<(), String> -where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, -{ - let Some(_field_a) = field_map.get(field_a.name.as_ref()) else { - return Err(format!( - "Unknown field '{}' on type '{}'", - field_a.name.as_ref(), - type_name - )); - }; - let Some(_field_b) = field_map.get(field_b.name.as_ref()) else { - return Err(format!( - "Unknown field '{}' on type '{}'", - field_b.name.as_ref(), - type_name - )); - }; - - has_same_type_shape( - &alias_or_name(field_a), - type_name, - &_field_a.type_, - &_field_b.type_, - )?; - - if field_a.name != field_b.name { - return Err(format!( - "Fields '{}' on type '{}' conflict because '{}' and '{}' are different fields", - alias_or_name(field_a), - type_name, - field_a.name.as_ref(), - field_b.name.as_ref(), - )); - } - - for (arg_a_name, arg_a_value) in field_a.arguments.iter() { - let arg_b_value = field_b.arguments.iter().find_map(|(name, value)| { - if name == arg_a_name { - Some(value) - } else { - None - } - }); - let args_match = match arg_b_value { - None => false, - Some(arg_b_value) => arg_b_value == arg_a_value, - }; - if !args_match { - return Err(format!( - "Fields '{}' on type '{}' conflict because they have differing arguments", - alias_or_name(field_a), - type_name, - )); - } - } - - Ok(()) -} - -pub fn has_same_type_shape( - field_name: &str, - type_name: &str, - type_a: &__Type, - type_b: &__Type, -) -> Result<(), String> { - let mut type_a = type_a; - let mut type_b = type_b; - - if matches!(type_a, __Type::NonNull(_)) || matches!(type_b, __Type::NonNull(_)) { - if let (__Type::NonNull(nullable_type_a), __Type::NonNull(nullable_type_b)) = - (type_a, type_b) - { - type_a = nullable_type_a.type_.as_ref(); - type_b = nullable_type_b.type_.as_ref(); - } else { - return Err(format!( - "Fields '{}' on type '{}' conflict because only one is non nullable", - field_name, type_name, - )); - } - } - - if matches!(type_a, __Type::List(_)) || matches!(type_b, __Type::List(_)) { - if let (__Type::List(list_type_a), __Type::List(list_type_b)) = (type_a, type_b) { - type_a = list_type_a.type_.as_ref(); - type_b = list_type_b.type_.as_ref(); - } else { - return Err(format!( - "Fields '{}' on type '{}' conflict because only one is a list type", - field_name, type_name, - )); - } - - return has_same_type_shape(field_name, type_name, type_a, type_b); - } - - if matches!(type_a, __Type::Enum(_)) - || matches!(type_b, __Type::Enum(_)) - || matches!(type_a, __Type::Scalar(_)) - || matches!(type_b, __Type::Scalar(_)) - { - return if type_a == type_b { - Ok(()) - } else { - Err(format!( - "Fields '{}' on type '{}' conflict due to mismatched types", - field_name, type_name, - )) - }; - } - - // TODO handle composite types? - - // Subfield type shapes will be checked on a later pass. - Ok(()) -} - -pub fn min_encapsulating_span(a: (Pos, Pos), b: (Pos, Pos)) -> (Pos, Pos) { - (a.0.min(b.0), a.1.max(b.1)) -} - -pub fn normalize_selection_set<'a, T>( - selection_set: &SelectionSet<'a, T>, - fragment_definitions: &Vec>, +pub fn normalize_selection_set<'a, 'b, T>( + selection_set: &'b SelectionSet<'a, T>, + fragment_definitions: &'b Vec>, type_name: &String, // for inline fragments variables: &serde_json::Value, // for directives - field_type: &__Type, -) -> Result>, String> +) -> Result>, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { - let mut normalized_fields: Vec> = vec![]; - - let field_map = field_map(&field_type.unmodified_type()); + let mut selections: Vec<&'b Field<'a, T>> = vec![]; for selection in &selection_set.items { - match normalize_selection( - selection, - fragment_definitions, - type_name, - variables, - field_type, - ) { - Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?, + let sel = selection; + match normalize_selection(sel, fragment_definitions, type_name, variables) { + Ok(sels) => selections.extend(sels), Err(err) => return Err(err), } } - Ok(normalized_fields) + Ok(selections) } /// Combines @skip and @include -pub fn selection_is_skipped<'a, T>( - query_selection: &Selection<'a, T>, +pub fn selection_is_skipped<'a, 'b, T>( + query_selection: &'b Selection<'a, T>, variables: &serde_json::Value, ) -> Result where - T: Text<'a> + Eq + AsRef + std::fmt::Debug, + T: Text<'a> + Eq + AsRef, { let directives = match query_selection { Selection::Field(x) => &x.directives, @@ -318,27 +130,24 @@ where } /// Normalizes literal selections, fragment spreads, and inline fragments -pub fn normalize_selection<'a, T>( - query_selection: &Selection<'a, T>, - fragment_definitions: &Vec>, +pub fn normalize_selection<'a, 'b, T>( + query_selection: &'b Selection<'a, T>, + fragment_definitions: &'b Vec>, type_name: &String, // for inline fragments variables: &serde_json::Value, // for directives - field_type: &__Type, // for field merging shape check -) -> Result>, String> +) -> Result>, String> where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { - let mut normalized_fields: Vec> = vec![]; + let mut selections: Vec<&Field<'a, T>> = vec![]; if selection_is_skipped(query_selection, variables)? { - return Ok(normalized_fields); + return Ok(selections); } - let field_map = field_map(&field_type.unmodified_type()); - match query_selection { Selection::Field(field) => { - merge_field(&mut normalized_fields, field.clone(), type_name, &field_map)?; + selections.push(field); } Selection::FragmentSpread(fragment_spread) => { let frag_name = &fragment_spread.fragment_name; @@ -364,15 +173,14 @@ where }; // TODO handle directives? - let frag_fields = normalize_selection_set( + let frag_selections = normalize_selection_set( &frag_def.selection_set, fragment_definitions, type_name, variables, - field_type, ); - match frag_fields { - Ok(fields) => merge_fields(&mut normalized_fields, fields, type_name, &field_map)?, + match frag_selections { + Ok(sels) => selections.extend(sels.iter()), Err(err) => return Err(err), }; } @@ -385,19 +193,18 @@ where }; if inline_fragment_applies { - let infrag_fields = normalize_selection_set( + let infrag_selections = normalize_selection_set( &inline_fragment.selection_set, fragment_definitions, type_name, variables, - field_type, )?; - merge_fields(&mut normalized_fields, infrag_fields, type_name, &field_map)?; + selections.extend(infrag_selections.iter()); } } } - Ok(normalized_fields) + Ok(selections) } pub fn to_gson<'a, T>( @@ -406,7 +213,7 @@ pub fn to_gson<'a, T>( variable_definitions: &Vec>, ) -> Result where - T: Text<'a> + AsRef + std::fmt::Debug, + T: Text<'a> + AsRef, { let result = match graphql_value { Value::Null => gson::Value::Null, @@ -466,6 +273,7 @@ where } pub fn validate_arg_from_type(type_: &__Type, value: &gson::Value) -> Result { + use crate::graphql::Scalar; use crate::gson::Number as GsonNumber; use crate::gson::Value as GsonValue; @@ -676,6 +484,7 @@ pub fn validate_arg_from_input_object( input_type: &__Type, value: &gson::Value, ) -> Result { + use crate::graphql::__TypeKind; use crate::gson::Value as GsonValue; let input_type_name = input_type.name().unwrap_or_default(); diff --git a/src/resolve.rs b/src/resolve.rs index ee77dd3d..bc151db4 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -22,7 +22,7 @@ pub fn resolve_inner<'a, T>( schema: &__Schema, ) -> GraphQLResponse where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { match variables { serde_json::Value::Object(_) => (), @@ -123,14 +123,14 @@ where } } -fn resolve_query<'a, T>( +fn resolve_query<'a, 'b, T>( query: Query<'a, T>, schema_type: &__Schema, variables: &Value, fragment_definitions: Vec>, ) -> GraphQLResponse where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let variable_definitions = &query.variable_definitions; resolve_selection_set( @@ -142,7 +142,7 @@ where ) } -fn resolve_selection_set<'a, T>( +fn resolve_selection_set<'a, 'b, T>( selection_set: SelectionSet<'a, T>, schema_type: &__Schema, variables: &Value, @@ -150,7 +150,7 @@ fn resolve_selection_set<'a, T>( variable_definitions: &Vec>, ) -> GraphQLResponse where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { use crate::graphql::*; @@ -163,7 +163,6 @@ where &fragment_definitions, &query_type_name, variables, - &query_type, ) { Ok(selections) => selections, Err(err) => { @@ -331,14 +330,14 @@ where } } -fn resolve_mutation<'a, T>( +fn resolve_mutation<'a, 'b, T>( query: Mutation<'a, T>, schema_type: &__Schema, variables: &Value, fragment_definitions: Vec>, ) -> GraphQLResponse where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { let variable_definitions = &query.variable_definitions; resolve_mutation_selection_set( @@ -350,7 +349,7 @@ where ) } -fn resolve_mutation_selection_set<'a, T>( +fn resolve_mutation_selection_set<'a, 'b, T>( selection_set: SelectionSet<'a, T>, schema_type: &__Schema, variables: &Value, @@ -358,7 +357,7 @@ fn resolve_mutation_selection_set<'a, T>( variable_definitions: &Vec>, ) -> GraphQLResponse where - T: Text<'a> + Eq + AsRef + std::fmt::Debug + Clone, + T: Text<'a> + Eq + AsRef, { use crate::graphql::*; @@ -384,7 +383,6 @@ where &fragment_definitions, &mutation_type_name, variables, - &mutation_type, ) { Ok(selections) => selections, Err(err) => { diff --git a/src/sql_types.rs b/src/sql_types.rs index 045e8e64..3cc914d6 100644 --- a/src/sql_types.rs +++ b/src/sql_types.rs @@ -249,7 +249,8 @@ impl<'a> ArgsIterator<'a> { debug_assert!(num_default_args <= num_total_args); let start_idx = num_total_args - num_default_args; for i in start_idx..num_total_args { - defaults[i] = Self::sql_to_graphql_default(default_strs[i - start_idx], arg_types[i]) + defaults[i] = + Self::sql_to_graphql_default(default_strs[i - start_idx], arg_types[i]) } defaults diff --git a/test/expected/issue_237_field_merging.out b/test/expected/issue_237_field_merging.out deleted file mode 100644 index e4b6e369..00000000 --- a/test/expected/issue_237_field_merging.out +++ /dev/null @@ -1,72 +0,0 @@ -begin; - -- https://github.com/supabase/pg_graphql/issues/237 - create table blog_post( - id int primary key, - a text, - b text, - c text, - d text, - e text, - f text - ); - insert into public.blog_post - values (1, 'a', 'b', 'c', 'd', 'e', 'f'); - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - edges { - node { - a - ...c_query - ... @include(if: true) { - e - } - } - } - } - blogPostCollection { - edges { - node { - b - ...d_query - ... @include(if: true) { - f - } - } - } - } - } - - fragment c_query on BlogPost { - c - } - - fragment d_query on BlogPost { - d - } - $$) - ); - jsonb_pretty ------------------------------------ - { + - "data": { + - "blogPostCollection": { + - "edges": [ + - { + - "node": { + - "a": "a",+ - "b": "b",+ - "c": "c",+ - "d": "d",+ - "e": "e",+ - "f": "f" + - } + - } + - ] + - } + - } + - } -(1 row) - -rollback; diff --git a/test/expected/issue_237_field_merging_mismatched.out b/test/expected/issue_237_field_merging_mismatched.out deleted file mode 100644 index 251dfdb4..00000000 --- a/test/expected/issue_237_field_merging_mismatched.out +++ /dev/null @@ -1,110 +0,0 @@ -begin; - -- https://github.com/supabase/pg_graphql/issues/237 - create table blog_post( - id int primary key, - a text, - b text, - c text, - d text, - e text, - f text - ); - insert into public.blog_post - values (1, 'a', 'b', 'c', 'd', 'e', 'f'); - -- mismatched field names - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - edges { - node { - a - } - } - } - blogPostCollection { - edges { - node { - a: b - } - } - } - } - $$) - ); - jsonb_pretty ----------------------------------------------------------------------------------------------------------- - { + - "data": null, + - "errors": [ + - { + - "message": "Fields 'a' on type 'BlogPost' conflict because 'b' and 'a' are different fields"+ - } + - ] + - } -(1 row) - - -- mismatched arguments - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection(filter: { - id: { eq: 1 } - }) { - edges { - node { - a - } - } - } - blogPostCollection { - edges { - node { - b - } - } - } - } - $$) - ); - jsonb_pretty ---------------------------------------------------------------------------------------------------------------------- - { + - "errors": [ + - { + - "message": "Fields 'blogPostCollection' on type 'Query' conflict because they have differing arguments"+ - } + - ] + - } -(1 row) - - -- mismatched list to node - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - a: edges { - cursor - } - } - blogPostCollection { - a: pageInfo { - cursor: endCursor - } - } - } - $$) - ); - jsonb_pretty ------------------------------------------------------------------------------------------------------------ - { + - "data": null, + - "errors": [ + - { + - "message": "Fields 'a' on type 'BlogPostConnection' conflict because only one is a list type"+ - } + - ] + - } -(1 row) - -rollback; diff --git a/test/sql/issue_237_field_merging.sql b/test/sql/issue_237_field_merging.sql deleted file mode 100644 index 499e436a..00000000 --- a/test/sql/issue_237_field_merging.sql +++ /dev/null @@ -1,50 +0,0 @@ -begin; - -- https://github.com/supabase/pg_graphql/issues/237 - create table blog_post( - id int primary key, - a text, - b text, - c text, - d text, - e text, - f text - ); - insert into public.blog_post - values (1, 'a', 'b', 'c', 'd', 'e', 'f'); - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - edges { - node { - a - ...c_query - ... @include(if: true) { - e - } - } - } - } - blogPostCollection { - edges { - node { - b - ...d_query - ... @include(if: true) { - f - } - } - } - } - } - - fragment c_query on BlogPost { - c - } - - fragment d_query on BlogPost { - d - } - $$) - ); -rollback; diff --git a/test/sql/issue_237_field_merging_mismatched.sql b/test/sql/issue_237_field_merging_mismatched.sql deleted file mode 100644 index 4a3fbcd8..00000000 --- a/test/sql/issue_237_field_merging_mismatched.sql +++ /dev/null @@ -1,75 +0,0 @@ -begin; - -- https://github.com/supabase/pg_graphql/issues/237 - create table blog_post( - id int primary key, - a text, - b text, - c text, - d text, - e text, - f text - ); - insert into public.blog_post - values (1, 'a', 'b', 'c', 'd', 'e', 'f'); - -- mismatched field names - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - edges { - node { - a - } - } - } - blogPostCollection { - edges { - node { - a: b - } - } - } - } - $$) - ); - -- mismatched arguments - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection(filter: { - id: { eq: 1 } - }) { - edges { - node { - a - } - } - } - blogPostCollection { - edges { - node { - b - } - } - } - } - $$) - ); - -- mismatched list to node - select jsonb_pretty( - graphql.resolve($$ - query { - blogPostCollection { - a: edges { - cursor - } - } - blogPostCollection { - a: pageInfo { - cursor: endCursor - } - } - } - $$) - ); -rollback; From 90e350907475a8a3cbbbfa0c9894bbb4d1b07b5e Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Wed, 21 Feb 2024 08:27:15 -0600 Subject: [PATCH 2/2] pre-release version for introspection regression --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4bc2e9b..fccd2c45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,7 +1034,7 @@ dependencies = [ [[package]] name = "pg_graphql" -version = "1.5.0" +version = "1.5.1-mergeless" dependencies = [ "base64 0.13.1", "bimap", diff --git a/Cargo.toml b/Cargo.toml index af71e237..dfa9ce5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pg_graphql" -version = "1.5.0" +version = "1.5.1-mergeless" edition = "2021" [lib]