From 4708590c96a5bc3843510cf6124b53f0226f3a3f Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Fri, 10 May 2024 14:41:08 -0500 Subject: [PATCH] onConflict intospection working --- src/graphql.rs | 111 +++++++++++++++++++++++++++------------------ src/parser_util.rs | 1 + src/sql_types.rs | 31 ++++++++++++- 3 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/graphql.rs b/src/graphql.rs index 749a62fc..cdc6b124 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -1112,6 +1112,7 @@ pub enum EnumSource { Enum(Arc), FilterIs, TableColumns(Arc), + OnConflictTarget(Arc
), } #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -1429,40 +1430,43 @@ impl ___Type for MutationType { let table_base_type_name = self.schema.graphql_table_base_type_name(table); if self.schema.graphql_table_insert_types_are_valid(table) { + let mut args = vec![__InputValue { + name_: "objects".to_string(), + type_: __Type::NonNull(NonNullType { + type_: Box::new(__Type::List(ListType { + type_: Box::new(__Type::NonNull(NonNullType { + type_: Box::new(__Type::InsertInput(InsertInputType { + table: Arc::clone(table), + schema: Arc::clone(&self.schema), + })), + })), + })), + }), + description: None, + default_value: None, + sql_type: None, + }]; + + if table.has_upsert_support() { + args.push(__InputValue { + name_: "onConflict".to_string(), + type_: __Type::InsertOnConflictInput(InsertOnConflictType { + table: Arc::clone(table), + schema: Arc::clone(&self.schema), + }), + description: None, + default_value: None, + sql_type: None, + }); + } + f.push(__Field { name_: format!("insertInto{}Collection", table_base_type_name), type_: __Type::InsertResponse(InsertResponseType { table: Arc::clone(table), schema: Arc::clone(&self.schema), }), - args: vec![ - __InputValue { - name_: "objects".to_string(), - type_: __Type::NonNull(NonNullType { - type_: Box::new(__Type::List(ListType { - type_: Box::new(__Type::NonNull(NonNullType { - type_: Box::new(__Type::InsertInput(InsertInputType { - table: Arc::clone(table), - schema: Arc::clone(&self.schema), - })), - })), - })), - }), - description: None, - default_value: None, - sql_type: None, - }, - __InputValue { - name_: "onConflict".to_string(), - type_: __Type::InsertOnConflictInput(InsertOnConflictType { - table: Arc::clone(table), - schema: Arc::clone(&self.schema), - }), - description: None, - default_value: None, - sql_type: None, - }, - ], + args, description: Some(format!( "Adds one or more `{}` records to the collection", table_base_type_name @@ -1654,6 +1658,10 @@ impl ___Type for EnumType { "{}Field", self.schema.graphql_table_base_type_name(&table) )), + EnumSource::OnConflictTarget(table) => Some(format!( + "{}OnConflictConstraint", + self.schema.graphql_table_base_type_name(&table) + )), } } @@ -1703,6 +1711,18 @@ impl ___Type for EnumType { deprecation_reason: None, }) .collect(), + EnumSource::OnConflictTarget(table) => { + table + .on_conflict_indexes() + .iter() + .map(|ix| __EnumValue { + // TODO, apply name restrictions + name: ix.name.clone(), + description: None, + deprecation_reason: None, + }) + .collect() + } }) } } @@ -3159,13 +3179,9 @@ impl ___Type for InsertOnConflictType { // If triggers are involved, we can't detect if a field is non-null. Default // all fields to non-null and let postgres errors handle it. type_: __Type::NonNull(NonNullType { - type_: Box::new(__Type::List(ListType { - type_: Box::new(__Type::NonNull(NonNullType { - type_: Box::new(__Type::Enum(EnumType { - enum_: EnumSource::TableColumns(Arc::clone(&self.table)), - schema: Arc::clone(&self.schema), - })), - })), + type_: Box::new(__Type::Enum(EnumType { + enum_: EnumSource::OnConflictTarget(Arc::clone(&self.table)), + schema: Arc::clone(&self.schema), })), }), description: Some( @@ -4141,15 +4157,22 @@ impl __Schema { table: Arc::clone(table), schema: Arc::clone(&schema_rc), })); - // Used by on conflict - types_.push(__Type::Enum(EnumType { - enum_: EnumSource::TableColumns(Arc::clone(table)), - schema: Arc::clone(&schema_rc), - })); - types_.push(__Type::InsertOnConflictInput(InsertOnConflictType { - table: Arc::clone(table), - schema: Arc::clone(&schema_rc), - })); + + // Used exclusively by onConflict + if table.has_upsert_support() { + types_.push(__Type::InsertOnConflictInput(InsertOnConflictType { + table: Arc::clone(table), + schema: Arc::clone(&schema_rc), + })); + types_.push(__Type::Enum(EnumType { + enum_: EnumSource::TableColumns(Arc::clone(table)), + schema: Arc::clone(&schema_rc), + })); + types_.push(__Type::Enum(EnumType { + enum_: EnumSource::OnConflictTarget(Arc::clone(table)), + schema: Arc::clone(&schema_rc), + })); + } } if self.graphql_table_update_types_are_valid(table) { diff --git a/src/parser_util.rs b/src/parser_util.rs index 49362ed5..7ec6fbea 100644 --- a/src/parser_util.rs +++ b/src/parser_util.rs @@ -414,6 +414,7 @@ pub fn validate_arg_from_type(type_: &__Type, value: &gson::Value) -> Result value.clone(), // TODO(or): Do I need to check directives here? EnumSource::TableColumns(_e) => value.clone(), + EnumSource::OnConflictTarget(_e) => value.clone(), } } None => return Err(format!("Invalid input for {} type", enum_name)), diff --git a/src/sql_types.rs b/src/sql_types.rs index cc0b0268..5640a325 100644 --- a/src/sql_types.rs +++ b/src/sql_types.rs @@ -531,7 +531,7 @@ impl Table { column_names: column_names.clone(), is_unique: true, is_primary_key: true, - name: "dummy".to_string(), + name: "NOT REQUIRED".to_string(), }) } } else { @@ -539,6 +539,31 @@ impl Table { } } + pub fn on_conflict_indexes(&self) -> Vec<&Index> { + // Indexes that are valid targets for an on conflict clause + // must be unique, real (not comment directives), and must + // not contain serial or generated columns because we don't + // allow those to be set in insert statements + let unique_indexes = self.indexes.iter().filter(|x| x.is_unique); + + let allowed_column_names = self + .columns + .iter() + .filter(|x| x.permissions.is_insertable) + .filter(|x| !x.is_generated) + .filter(|x| !x.is_serial) + .map(|x| &x.name) + .collect::>(); + + unique_indexes + .filter(|uix| { + uix.column_names + .iter() + .all(|col_name| allowed_column_names.contains(col_name)) + }) + .collect::>() + } + pub fn primary_key_columns(&self) -> Vec<&Arc> { self.primary_key() .map(|x| x.column_names) @@ -553,6 +578,10 @@ impl Table { .collect::>>() } + pub fn has_upsert_support(&self) -> bool { + self.on_conflict_indexes().len() > 0 + } + pub fn is_any_column_selectable(&self) -> bool { self.columns.iter().any(|x| x.permissions.is_selectable) }