From 8da22dc8b1849ab9b2947c39a5f96d321281ce99 Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Mon, 26 Aug 2024 11:49:00 -0500 Subject: [PATCH] on conflict functional --- src/builder.rs | 42 ++++++++++++++++++++++++++++++++++++------ src/graphql.rs | 1 - src/parser_util.rs | 5 ++++- src/transpile.rs | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 149aa4ad..6e07f7af 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -200,7 +200,7 @@ where variable_definitions, )?; - let insert_type: OnConflictType = match field.get_arg("onConflict") { + let conflict_type: OnConflictType = match field.get_arg("onConflict") { None => return Ok(None), Some(x) => match x.type_().unmodified_type() { __Type::OnConflictInput(insert_on_conflict) => insert_on_conflict, @@ -208,9 +208,6 @@ where }, }; - let filter: FilterBuilder = - read_argument_filter(field, query_field, variables, variable_definitions)?; - let on_conflict_builder = match validated { gson::Value::Absent | gson::Value::Null => None, gson::Value::Object(contents) => { @@ -218,7 +215,7 @@ where .get("constraint") .expect("OnConflict revalidation error. Expected constraint") { - gson::Value::String(ix_name) => insert_type + gson::Value::String(ix_name) => conflict_type .table .indexes .iter() @@ -231,6 +228,36 @@ where } }; + // TODO: Filter reading logic is partially duplicated from read_argument_filter + // ideally this should be refactored + let filter_gson = contents + .get("filter") + .expect("onConflict revalidation error"); + + let filter = match filter_gson { + gson::Value::Null | gson::Value::Absent => FilterBuilder { elems: vec![] }, + gson::Value::Object(_) => { + let filter_type = conflict_type + .input_fields() + .expect("Failed to unwrap input fields on OnConflict type") + .iter() + .find(|in_f| in_f.name() == "filter") + .expect("Failed to get filter input_field on onConflict type") + .type_() + .unmodified_type(); + + if !matches!(filter_type, __Type::FilterEntity(_)) { + return Err("Could not locate Filter Entity type".to_string()); + } + let filter_field_map = input_field_map(&filter_type); + let filter_elems = create_filters(&filter_gson, &filter_field_map)?; + FilterBuilder { + elems: filter_elems, + } + } + _ => return Err("OnConflict revalidation error. invalid filter object".to_string()), + }; + let update_fields = match contents .get("updateFields") .expect("OnConflict revalidation error. Expected updateFields") @@ -240,7 +267,7 @@ where for col_name in col_names { match col_name { gson::Value::String(c) => { - let col = insert_type.table.columns.iter().find(|column| &column.name == c).expect("OnConflict revalidation error. updateFields: unknown column name"); + let col = conflict_type.table.columns.iter().find(|column| &column.name == c).expect("OnConflict revalidation error. updateFields: unknown column name"); update_columns.insert(Arc::clone(col)); } _ => return Err("OnConflict revalidation error. Expected updateFields to be column names".to_string()), @@ -1145,11 +1172,14 @@ where variable_definitions, )?; + //return Err(format!("Err {:?}", validated)); + let filter_type = field .get_arg("filter") .expect("failed to get filter argument") .type_() .unmodified_type(); + if !matches!(filter_type, __Type::FilterEntity(_)) { return Err("Could not locate Filter Entity type".to_string()); } diff --git a/src/graphql.rs b/src/graphql.rs index 0fd0c36a..5422f4bb 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -3181,7 +3181,6 @@ impl ___Type for OnConflictType { fn input_fields(&self) -> Option> { Some(vec![ __InputValue { - // TODO: Create a custom type for available constraints name_: "constraint".to_string(), // 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. diff --git a/src/parser_util.rs b/src/parser_util.rs index f69bed96..2fac7fe7 100644 --- a/src/parser_util.rs +++ b/src/parser_util.rs @@ -529,7 +529,10 @@ pub fn validate_arg_from_input_object( match input_obj.get(&obj_field_key) { None => { - validate_arg_from_type(&obj_field_type, &GsonValue::Null)?; + // If there was no provided key, use "Absent" so all arguments + // always exist in the validated input datat + validate_arg_from_type(&obj_field_type, &GsonValue::Absent)?; + out_map.insert(obj_field_key, GsonValue::Absent); } Some(x) => { let out_val = validate_arg_from_type(&obj_field_type, x)?; diff --git a/src/transpile.rs b/src/transpile.rs index fc14749a..3694f377 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -309,11 +309,45 @@ impl MutationEntrypoint<'_> for InsertBuilder { let values_clause = values_rows_clause.join(", "); + let insert_quoted_block_name = rand_block_name(); + let on_conflict_clause = match &self.on_conflict { + Some(on_conflict) => { + let constraint_name = &on_conflict.constraint.name; + let do_update_set_clause = on_conflict + .update_fields + .iter() + .map(|col| { + format!( + "{} = excluded.{}", + quote_ident(&col.name), + quote_ident(&col.name), + ) + }) + .join(", "); + + let conflict_where_clause = on_conflict.filter.to_where_clause( + &insert_quoted_block_name, + &self.table, + param_context, + )?; + + format!( + " + on conflict on constraint {constraint_name} + do update set {do_update_set_clause} + where {conflict_where_clause} + ", + ) + } + None => "".to_string(), + }; + Ok(format!( " with affected as ( - insert into {quoted_schema}.{quoted_table}({referenced_columns_clause}) + insert into {quoted_schema}.{quoted_table} as {insert_quoted_block_name} ({referenced_columns_clause}) values {values_clause} + {on_conflict_clause} returning {selectable_columns_clause} ) select