Repository: prisma/prisma-engines
Pull request #5381: feat: add ProjectedDataSinkDependency and minor fixes in graph building
Author: login: jacek-prisma
Created: 2025-05-06T14:44:24Z | Merged: 2025-05-06T19:23:05Z

Adds a new edge type `ProjectedDataSinkDependency` and integrates it into some queries and also contains some minor fixes that close [ORM-913](https://linear.app/prisma-company/issue/ORM-913/fix-expected-an-array-or-an-object-got-undefined)


Diff:
diff --git a/query-compiler/query-compiler/src/translate.rs b/query-compiler/query-compiler/src/translate.rs
index 9c759c04efc5..1f1fe21a1867 100644
--- a/query-compiler/query-compiler/src/translate.rs
+++ b/query-compiler/query-compiler/src/translate.rs
@@ -6,8 +6,8 @@ use crate::data_mapper::map_result_structure;
 use itertools::{Either, Itertools};
 use query::translate_query;
 use query_builder::QueryBuilder;
-use query_core::{EdgeRef, Node, NodeRef, Query, QueryGraph, QueryGraphBuilderError, QueryGraphDependency};
-use query_structure::{PrismaValue, PrismaValueType, SelectedField, SelectionResult};
+use query_core::{DataSink, EdgeRef, Node, NodeRef, Query, QueryGraph, QueryGraphBuilderError, QueryGraphDependency};
+use query_structure::{FieldSelection, PrismaValue, PrismaValueType, SelectedField, SelectionResult};
 use thiserror::Error;
 
 #[derive(Debug, Error)]
@@ -76,6 +76,7 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
     }
 
     fn translate(&mut self) -> TranslateResult<Expression> {
+        self.graph.mark_visited(&self.node);
         let node = self
             .graph
             .node_content(&self.node)
@@ -84,14 +85,15 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
         match node {
             Node::Query(_) => self.translate_query(),
             // might be worth having Expression::Unit for this?
-            Node::Empty => Ok(Expression::Seq(vec![])),
+            Node::Empty => {
+                let children = self.translate_children()?;
+                Ok(Expression::Seq(children))
+            }
             n => unimplemented!("{:?}", std::mem::discriminant(n)),
         }
     }
 
-    fn translate_query(&mut self) -> TranslateResult<Expression> {
-        self.graph.mark_visited(&self.node);
-
+    fn translate_children(&mut self) -> TranslateResult<Vec<Expression>> {
         // Don't recurse into children if the current node is already a result node.
         let children = if !self.graph.is_result_node(&self.node) {
             self.process_children()?
@@ -99,30 +101,37 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
             Vec::new()
         };
 
+        Ok(children)
+    }
+
+    fn translate_query(&mut self) -> TranslateResult<Expression> {
+        let children = self.translate_children()?;
+
         let mut node = self.graph.pluck_node(&self.node);
 
         for edge in self.parent_edges {
             match self.graph.pluck_edge(edge) {
                 QueryGraphDependency::ExecutionOrder => {}
                 QueryGraphDependency::ProjectedDataDependency(selection, f, _) => {
-                    let fields = selection
-                        .selections()
-                        .map(|field| {
-                            (
-                                field.clone(),
-                                PrismaValue::Placeholder {
-                                    name: generate_projected_dependency_name(self.graph.edge_source(edge), field),
-                                    r#type: PrismaValueType::Any,
-                                },
-                            )
-                        })
-                        .collect_vec();
+                    let fields = self.process_edge_selections(edge, selection);
 
                     // TODO: there are cases where we look at the number of results in some
                     // dependencies, these won't work with the current implementation and will
                     // need to be re-implemented
                     node = f(node, vec![SelectionResult::new(fields)])?;
                 }
+                QueryGraphDependency::ProjectedDataSinkDependency(selection, sink, _) => {
+                    let fields = self.process_edge_selections(edge, selection);
+
+                    match sink {
+                        DataSink::AllRows(field) | DataSink::SingleRowArray(field) => {
+                            *field.node_input_field(&mut node) = vec![SelectionResult::new(fields)];
+                        }
+                        DataSink::SingleRow(field) => {
+                            *field.node_input_field(&mut node) = SelectionResult::new(fields);
+                        }
+                    }
+                }
                 // TODO: implement data dependencies and if/else
                 QueryGraphDependency::DataDependency(_) => todo!(),
                 QueryGraphDependency::DiffLeftDataDependency(_) => todo!(),
@@ -227,12 +236,24 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
             .incoming_edges(&node)
             .into_iter()
             .flat_map(|edge| {
-                let Some(QueryGraphDependency::ProjectedDataDependency(selection, _, expectation)) =
-                    self.graph.edge_content(&edge)
+                let edge_content = self.graph.edge_content(&edge);
+                let Some(
+                    QueryGraphDependency::ProjectedDataDependency(selection, _, expectation)
+                    | QueryGraphDependency::ProjectedDataSinkDependency(selection, _, expectation),
+                ) = edge_content
                 else {
                     return Either::Left(std::iter::empty());
                 };
 
+                let requires_unique = matches!(
+                    edge_content,
+                    Some(QueryGraphDependency::ProjectedDataSinkDependency(
+                        _,
+                        DataSink::SingleRow(_),
+                        _
+                    ))
+                );
+
                 let source = self.graph.edge_source(&edge);
 
                 Either::Right(selection.selections().map(move |field| {
@@ -246,6 +267,12 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
                         },
                         None => expr,
                     };
+                    let expr = if requires_unique {
+                        Expression::Unique(expr.into())
+                    } else {
+                        expr
+                    };
+
                     Binding::new(
                         generate_projected_dependency_name(source, field),
                         Expression::MapField {
@@ -271,6 +298,25 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
             Ok(expr)
         }
     }
+
+    fn process_edge_selections(
+        &mut self,
+        edge: &EdgeRef,
+        selection: FieldSelection,
+    ) -> Vec<(SelectedField, PrismaValue)> {
+        selection
+            .selections()
+            .map(|field| {
+                (
+                    field.clone(),
+                    PrismaValue::Placeholder {
+                        name: generate_projected_dependency_name(self.graph.edge_source(edge), field),
+                        r#type: PrismaValueType::Any,
+                    },
+                )
+            })
+            .collect_vec()
+    }
 }
 
 fn generate_projected_dependency_name(source: NodeRef, field: &SelectedField) -> String {
diff --git a/query-engine/core/src/interpreter/expressionista.rs b/query-engine/core/src/interpreter/expressionista.rs
index 0b908bd637cd..c3bb9c51ef84 100644
--- a/query-engine/core/src/interpreter/expressionista.rs
+++ b/query-engine/core/src/interpreter/expressionista.rs
@@ -346,7 +346,7 @@ impl Expressionista {
                         let node: InterpretationResult<Node> =
                             parent_id_deps
                                 .into_iter()
-                                .try_fold(node, |node, (parent_binding_name, dependency)| {
+                                .try_fold(node, |mut node, (parent_binding_name, dependency)| {
                                     let binding = match env.get(&parent_binding_name) {
                                         Some(binding) => Ok(binding),
                                         None => Err(InterpreterError::EnvVarNotFound(format!(
@@ -364,6 +364,36 @@ impl Expressionista {
                                             })
                                         }
 
+                                        QueryGraphDependency::ProjectedDataSinkDependency(
+                                            selection,
+                                            sink,
+                                            expectation,
+                                        ) => binding.as_selection_results(&selection).and_then(
+                                            |mut parent_selections| {
+                                                if let Some(expectation) = expectation {
+                                                    expectation.check(&parent_selections)?;
+                                                }
+                                                match sink {
+                                                    DataSink::AllRows(field) => {
+                                                        *field.node_input_field(&mut node) = parent_selections
+                                                    }
+                                                    DataSink::SingleRow(field) => {
+                                                        let row = parent_selections.pop().expect(
+                                                            "parent selection should be present after validation",
+                                                        );
+                                                        *field.node_input_field(&mut node) = row;
+                                                    }
+                                                    DataSink::SingleRowArray(field) => {
+                                                        let row = parent_selections.pop().expect(
+                                                            "parent selection should be present after validation",
+                                                        );
+                                                        *field.node_input_field(&mut node) = vec![row];
+                                                    }
+                                                }
+                                                Ok(node)
+                                            },
+                                        ),
+
                                         QueryGraphDependency::DataDependency(f) => Ok(f(node, binding)?),
 
                                         QueryGraphDependency::DiffLeftDataDependency(f) => {
@@ -402,6 +432,7 @@ impl Expressionista {
             .filter_map(|edge| match graph.pluck_edge(&edge) {
                 x @ (QueryGraphDependency::DataDependency(_)
                 | QueryGraphDependency::ProjectedDataDependency(_, _, _)
+                | QueryGraphDependency::ProjectedDataSinkDependency(_, _, _)
                 | QueryGraphDependency::DiffLeftDataDependency(_)
                 | QueryGraphDependency::DiffRightDataDependency(_)) => {
                     let parent_binding_name = graph.edge_source(&edge).id();
diff --git a/query-engine/core/src/query_graph/formatters.rs b/query-engine/core/src/query_graph/formatters.rs
index e4c825c9cd3a..31677ca644f0 100644
--- a/query-engine/core/src/query_graph/formatters.rs
+++ b/query-engine/core/src/query_graph/formatters.rs
@@ -118,6 +118,16 @@ impl Display for QueryGraphDependency {
                         .collect::<Vec<_>>()
                 )
             }
+            Self::ProjectedDataSinkDependency(selection, _, _) => {
+                write!(
+                    f,
+                    "ProjectedDataSinkDependency ({:?})",
+                    selection
+                        .selections()
+                        .map(|f| format!("{}.{}", f.container().name(), f.prisma_name()))
+                        .collect::<Vec<_>>()
+                )
+            }
             Self::DiffLeftDataDependency(_) => write!(f, "DiffLeftResult"),
             Self::DiffRightDataDependency(_) => write!(f, "DiffRightResult"),
             Self::Then => write!(f, "Then"),
diff --git a/query-engine/core/src/query_graph/mod.rs b/query-engine/core/src/query_graph/mod.rs
index e73a29613698..256dbb716800 100644
--- a/query-engine/core/src/query_graph/mod.rs
+++ b/query-engine/core/src/query_graph/mod.rs
@@ -158,6 +158,8 @@ pub enum QueryGraphDependency {
     /// See `insert_reloads` for more information.
     ProjectedDataDependency(FieldSelection, ProjectedDataDependencyFn, Option<DataExpectation>), // [Composites] todo rename
 
+    ProjectedDataSinkDependency(FieldSelection, DataSink, Option<DataExpectation>),
+
     /// Specialized version of `DataDependency` that accepts the the difference
     /// between the left and right side of a parent diff operation.
     DiffLeftDataDependency(DiffDataDependencyFn),
@@ -173,6 +175,16 @@ pub enum QueryGraphDependency {
     Else,
 }
 
+pub enum DataSink {
+    AllRows(&'static dyn NodeInputField<Vec<SelectionResult>>),
+    SingleRow(&'static dyn NodeInputField<SelectionResult>),
+    SingleRowArray(&'static dyn NodeInputField<Vec<SelectionResult>>),
+}
+
+pub trait NodeInputField<R>: Send + Sync {
+    fn node_input_field<'a>(&self, node: &'a mut Node) -> &'a mut R;
+}
+
 /// An expectation for a data dependency.
 pub struct DataExpectation {
     rules: SmallVec<[DataRule; 1]>,
@@ -762,7 +774,8 @@ impl QueryGraph {
             let dependencies: Vec<FieldSelection> = out_edges
                 .into_iter()
                 .filter_map(|edge| match self.edge_content(&edge).unwrap() {
-                    QueryGraphDependency::ProjectedDataDependency(ref requested_selection, _, _) => {
+                    QueryGraphDependency::ProjectedDataDependency(ref requested_selection, _, _)
+                    | QueryGraphDependency::ProjectedDataSinkDependency(ref requested_selection, _, _) => {
                         Some(requested_selection.clone())
                     }
                     _ => None,
@@ -776,7 +789,10 @@ impl QueryGraph {
             let incoming_dep_edge = in_edges.into_iter().find(|edge| {
                 matches!(
                     self.edge_content(edge),
-                    Some(QueryGraphDependency::ProjectedDataDependency(_, _, _))
+                    Some(
+                        QueryGraphDependency::ProjectedDataDependency(_, _, _)
+                            | QueryGraphDependency::ProjectedDataSinkDependency(_, _, _)
+                    )
                 )
             });
 
@@ -787,14 +803,28 @@ impl QueryGraph {
                     .remove_edge(incoming_edge)
                     .expect("Expected edges between marked nodes to be non-empty.");
 
-                if let QueryGraphDependency::ProjectedDataDependency(existing, transformer, expectation) = content {
-                    let merged_dependencies = dependencies.merge(existing);
-
-                    self.create_edge(
-                        &source,
-                        &target,
-                        QueryGraphDependency::ProjectedDataDependency(merged_dependencies, transformer, expectation),
-                    )?;
+                match content {
+                    QueryGraphDependency::ProjectedDataDependency(existing, transformer, expectation) => {
+                        let merged_dependencies = dependencies.merge(existing);
+                        self.create_edge(
+                            &source,
+                            &target,
+                            QueryGraphDependency::ProjectedDataDependency(
+                                merged_dependencies,
+                                transformer,
+                                expectation,
+                            ),
+                        )?;
+                    }
+                    QueryGraphDependency::ProjectedDataSinkDependency(existing, sink, expectation) => {
+                        let merged_dependencies = dependencies.merge(existing);
+                        self.create_edge(
+                            &source,
+                            &target,
+                            QueryGraphDependency::ProjectedDataSinkDependency(merged_dependencies, sink, expectation),
+                        )?;
+                    }
+                    _ => (),
                 }
             }
         }
@@ -1005,6 +1035,7 @@ impl QueryGraph {
                         .into_iter()
                         .filter_map(|edge| match self.edge_content(&edge).unwrap() {
                             QueryGraphDependency::ProjectedDataDependency(ref requested_selection, _, _)
+                            | QueryGraphDependency::ProjectedDataSinkDependency(ref requested_selection, _, _)
                                 if !q.satisfies(requested_selection) =>
                             {
                                 Some(requested_selection.clone())
diff --git a/query-engine/core/src/query_graph_builder/inputs.rs b/query-engine/core/src/query_graph_builder/inputs.rs
new file mode 100644
index 000000000000..4d704e2d1f98
--- /dev/null
+++ b/query-engine/core/src/query_graph_builder/inputs.rs
@@ -0,0 +1,29 @@
+use query_structure::SelectionResult;
+
+use crate::{Node, NodeInputField, Query, WriteQuery};
+
+#[derive(Debug)]
+pub(crate) struct UpdateRecordFilterInput;
+
+impl NodeInputField<Vec<SelectionResult>> for UpdateRecordFilterInput {
+    fn node_input_field<'a>(&self, node: &'a mut crate::Node) -> &'a mut Vec<SelectionResult> {
+        if let Node::Query(Query::Write(WriteQuery::UpdateRecord(ref mut ur))) = node {
+            ur.record_filter_mut().selectors.get_or_insert_default()
+        } else {
+            panic!("UpdateRecordFilterInput can only be used with UpdateRecord node")
+        }
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct UpdateManyRecordsFilterInput;
+
+impl NodeInputField<Vec<SelectionResult>> for UpdateManyRecordsFilterInput {
+    fn node_input_field<'a>(&self, node: &'a mut crate::Node) -> &'a mut Vec<SelectionResult> {
+        if let Node::Query(Query::Write(WriteQuery::UpdateManyRecords(ref mut ur))) = node {
+            ur.record_filter.selectors.get_or_insert_default()
+        } else {
+            panic!("UpdateManyRecordsFilterInput can only be used with UpdateManyRecords node")
+        }
+    }
+}
diff --git a/query-engine/core/src/query_graph_builder/mod.rs b/query-engine/core/src/query_graph_builder/mod.rs
index f9076a1b3a17..ec102c5f075b 100644
--- a/query-engine/core/src/query_graph_builder/mod.rs
+++ b/query-engine/core/src/query_graph_builder/mod.rs
@@ -4,6 +4,7 @@ mod builder;
 mod error;
 mod extractors;
 
+pub(crate) mod inputs;
 pub(crate) mod read;
 pub(crate) mod write;
 pub(crate) use extractors::*;
diff --git a/query-engine/core/src/query_graph_builder/write/nested/connect_nested.rs b/query-engine/core/src/query_graph_builder/write/nested/connect_nested.rs
index e5b743734c18..84b8e451a092 100644
--- a/query-engine/core/src/query_graph_builder/write/nested/connect_nested.rs
+++ b/query-engine/core/src/query_graph_builder/write/nested/connect_nested.rs
@@ -624,36 +624,30 @@ fn handle_one_to_one_parent_create(
             ),
         )?;
 
-        let relation_name = parent_relation_field.relation().name();
-        let parent_model_name = parent_relation_field.model().name().to_owned();
-        let child_model_name = child_model.name().to_owned();
-
         graph.create_edge(
-             &parent_node,
-             &update_children_node,
-             QueryGraphDependency::ProjectedDataDependency(parent_linking_fields, Box::new(move |mut update_children_node, mut parent_links| {
-                 let parent_link = match parent_links.pop() {
-                     Some(link) => Ok(link),
-                     None => Err(QueryGraphBuilderError::RecordNotFound(format!(
-                        "No '{parent_model_name}' record (needed to update inlined relation on '{child_model_name}') was found for a nested connect on one-to-one relation '{relation_name}'."
-                    ))),
-                 }?;
-
-                 if let Node::Query(Query::Write(ref mut wq)) = update_children_node {
-                     wq.inject_result_into_args(child_linking_fields.assimilate(parent_link)?);
-                 }
-
-                 Ok(update_children_node)
-             }),
-             Some(DataExpectation::non_empty_rows(
-                 MissingRelatedRecord::builder()
-                     .model(&parent_relation_field.model())
-                     .relation(&parent_relation_field.relation())
-                     .needed_for(DependentOperation::update_inlined_relation(child_model))
-                     .operation(DataOperation::NestedConnect)
-                     .build(),
-             ))),
-         )?;
+            &parent_node,
+            &update_children_node,
+            QueryGraphDependency::ProjectedDataDependency(
+                parent_linking_fields,
+                Box::new(move |mut update_children_node, mut parent_links| {
+                    let parent_link = parent_links.pop().expect("parent link should be present");
+
+                    if let Node::Query(Query::Write(ref mut wq)) = update_children_node {
+                        wq.inject_result_into_args(child_linking_fields.assimilate(parent_link)?);
+                    }
+
+                    Ok(update_children_node)
+                }),
+                Some(DataExpectation::non_empty_rows(
+                    MissingRelatedRecord::builder()
+                        .model(&parent_relation_field.model())
+                        .relation(&parent_relation_field.relation())
+                        .needed_for(DependentOperation::update_inlined_relation(child_model))
+                        .operation(DataOperation::NestedConnect)
+                        .build(),
+                )),
+            ),
+        )?;
     }
 
     Ok(())
diff --git a/query-engine/core/src/query_graph_builder/write/nested/update_nested.rs b/query-engine/core/src/query_graph_builder/write/nested/update_nested.rs
index d5df8a83e3fc..65e1aaa1bd95 100644
--- a/query-engine/core/src/query_graph_builder/write/nested/update_nested.rs
+++ b/query-engine/core/src/query_graph_builder/write/nested/update_nested.rs
@@ -1,11 +1,11 @@
 use super::*;
+use crate::inputs::{UpdateManyRecordsFilterInput, UpdateRecordFilterInput};
 use crate::query_graph_builder::write::update::UpdateManyRecordNodeOptionals;
-use crate::DataExpectation;
 use crate::{
-    query_ast::*,
-    query_graph::{Node, NodeRef, QueryGraph, QueryGraphDependency},
+    query_graph::{NodeRef, QueryGraph, QueryGraphDependency},
     ParsedInputValue,
 };
+use crate::{DataExpectation, DataSink};
 use query_structure::{Filter, Model, RelationFieldRef};
 use schema::constants::args;
 use std::convert::TryInto;
@@ -95,17 +95,9 @@ pub fn nested_update(
         graph.create_edge(
             &find_child_records_node,
             &update_node,
-            QueryGraphDependency::ProjectedDataDependency(
+            QueryGraphDependency::ProjectedDataSinkDependency(
                 child_model_identifier.clone(),
-                Box::new(move |mut update_node, mut child_ids| {
-                    let child_id = child_ids.pop().expect("child id should be present");
-
-                    if let Node::Query(Query::Write(WriteQuery::UpdateRecord(ref mut ur))) = update_node {
-                        ur.set_selectors(vec![child_id]);
-                    }
-
-                    Ok(update_node)
-                }),
+                DataSink::SingleRowArray(&UpdateRecordFilterInput),
                 Some(DataExpectation::non_empty_rows(
                     MissingRelatedRecord::builder()
                         .model(child_model)
@@ -159,16 +151,9 @@ pub fn nested_update_many(
         graph.create_edge(
             &find_child_records_node,
             &update_many_node,
-            QueryGraphDependency::ProjectedDataDependency(
+            QueryGraphDependency::ProjectedDataSinkDependency(
                 child_model_identifier.clone(),
-                Box::new(move |mut update_many_node, child_ids| {
-                    if let Node::Query(Query::Write(WriteQuery::UpdateManyRecords(ref mut ur))) = update_many_node {
-                        // ur.set_filter(Filter::and(vec![ur.filter.clone(), child_ids.filter()]));
-                        ur.record_filter = child_ids.into();
-                    }
-
-                    Ok(update_many_node)
-                }),
+                DataSink::AllRows(&UpdateManyRecordsFilterInput),
                 None,
             ),
         )?;
diff --git a/query-engine/core/src/query_graph_builder/write/nested/upsert_nested.rs b/query-engine/core/src/query_graph_builder/write/nested/upsert_nested.rs
index a716d4acc004..90ad5e4348d6 100644
--- a/query-engine/core/src/query_graph_builder/write/nested/upsert_nested.rs
+++ b/query-engine/core/src/query_graph_builder/write/nested/upsert_nested.rs
@@ -245,37 +245,32 @@ pub fn nested_upsert(
                 ),
             )?;
 
-            let parent_model_name = parent_relation_field.model().name().to_owned();
-            let child_model_name = parent_relation_field.related_model().name().to_owned();
-            let relation_name = parent_relation_field.relation().name().to_owned();
-
             // Edge to retrieve the child ID to inject
             graph.create_edge(
                 &create_node,
                 &update_node,
-                QueryGraphDependency::ProjectedDataDependency(child_link.clone(), Box::new(move |mut update_node, mut child_links| {
-                    let child_link = match child_links.pop() {
-                        Some(link) => Ok(link),
-                        None => Err(QueryGraphBuilderError::RecordNotFound(format!(
-                            "No '{child_model_name}' record (needed to update inlined relation on '{parent_model_name}') was found for a nested upsert on relation '{relation_name}'."
-                        ))),
-                    }?;
+                QueryGraphDependency::ProjectedDataDependency(
+                    child_link.clone(),
+                    Box::new(move |mut update_node, mut child_links| {
+                        let child_link = child_links.pop().expect("child link should be present");
 
-                    if let Node::Query(Query::Write(ref mut wq)) = update_node {
-                        wq.inject_result_into_args(parent_link.assimilate(child_link)?);
-                    }
+                        if let Node::Query(Query::Write(ref mut wq)) = update_node {
+                            wq.inject_result_into_args(parent_link.assimilate(child_link)?);
+                        }
 
-                    Ok(update_node)
-                }),  Some(DataExpectation::non_empty_rows(
-                    MissingRelatedRecord::builder()
-                        .model(&parent_relation_field.related_model())
-                        .relation(&parent_relation_field.relation())
-                        .needed_for(DependentOperation::update_inlined_relation(
-                            &parent_relation_field.model(),
-                        ))
-                        .operation(DataOperation::NestedUpsert)
-                        .build(),
-                ))),
+                        Ok(update_node)
+                    }),
+                    Some(DataExpectation::non_empty_rows(
+                        MissingRelatedRecord::builder()
+                            .model(&parent_relation_field.related_model())
+                            .relation(&parent_relation_field.relation())
+                            .needed_for(DependentOperation::update_inlined_relation(
+                                &parent_relation_field.model(),
+                            ))
+                            .operation(DataOperation::NestedUpsert)
+                            .build(),
+                    )),
+                ),
             )?;
         } else {
             // Inlined on child
diff --git a/query-engine/query-builders/sql-query-builder/src/write.rs b/query-engine/query-builders/sql-query-builder/src/write.rs
index 1c5aac08a115..9c9e38ee2813 100644
--- a/query-engine/query-builders/sql-query-builder/src/write.rs
+++ b/query-engine/query-builders/sql-query-builder/src/write.rs
@@ -239,14 +239,14 @@ pub fn generate_update_statements(
     limit: Option<usize>,
     ctx: &Context<'_>,
 ) -> Vec<Query<'static>> {
-    match record_filter.selectors {
+    let RecordFilter { filter, selectors } = record_filter;
+    match selectors {
         Some(ids) => {
-            let filter = record_filter.filter.clone();
             let slice = &ids[..limit.unwrap_or(ids.len()).min(ids.len())];
             update::update_many_from_ids_and_filter(model, filter, slice, args, selected_fields, ctx)
         }
         None => {
-            let query = update::update_many_from_filter(model, record_filter.filter, args, selected_fields, limit, ctx);
+            let query = update::update_many_from_filter(model, filter, args, selected_fields, limit, ctx);
             vec![query]
         }
     }


Reviews:
user: login: jacek-prisma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:46:21Z
user: login: jacek-prisma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:46:55Z
user: login: jacek-prisma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:47:12Z
user: login: jacek-prisma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:50:19Z
user: login: jacek-prisma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T15:22:58Z
user: login: aqrln
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T18:30:13Z
user: login: aqrln
body: LGTM modulo out-of-date name in the `Display` impl: https://github.com/prisma/prisma-engines/pull/5381#discussion_r2076041163
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-06T18:40:45Z

Review comments:
diff_hunk: @@ -84,45 +85,53 @@ impl<'a, 'b> NodeTranslator<'a, 'b> {
         match node {
             Node::Query(_) => self.translate_query(),
             // might be worth having Expression::Unit for this?
-            Node::Empty => Ok(Expression::Seq(vec![])),
+            Node::Empty => {
+                let children = self.translate_children()?;
+                Ok(Expression::Seq(children))
path: query-compiler/query-compiler/src/translate.rs
user: login: jacek-prisma
body: just ensures that we process the children of empty nodes, this does happen in referential actions and was the cause of ORM-913
created_at: 2025-05-06T14:46:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -624,36 +624,30 @@ fn handle_one_to_one_parent_create(
             ),
         )?;
 
-        let relation_name = parent_relation_field.relation().name();
-        let parent_model_name = parent_relation_field.model().name().to_owned();
-        let child_model_name = child_model.name().to_owned();
-
         graph.create_edge(
-             &parent_node,
-             &update_children_node,
-             QueryGraphDependency::ProjectedDataDependency(parent_linking_fields, Box::new(move |mut update_children_node, mut parent_links| {
-                 let parent_link = match parent_links.pop() {
-                     Some(link) => Ok(link),
-                     None => Err(QueryGraphBuilderError::RecordNotFound(format!(
-                        "No '{parent_model_name}' record (needed to update inlined relation on '{child_model_name}') was found for a nested connect on one-to-one relation '{relation_name}'."
-                    ))),
-                 }?;
path: query-engine/core/src/query_graph_builder/write/nested/connect_nested.rs
user: login: jacek-prisma
body: just removing code that's no longer needed
created_at: 2025-05-06T14:46:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -245,37 +245,32 @@ pub fn nested_upsert(
                 ),
             )?;
 
-            let parent_model_name = parent_relation_field.model().name().to_owned();
-            let child_model_name = parent_relation_field.related_model().name().to_owned();
-            let relation_name = parent_relation_field.relation().name().to_owned();
-
             // Edge to retrieve the child ID to inject
             graph.create_edge(
                 &create_node,
                 &update_node,
-                QueryGraphDependency::ProjectedDataDependency(child_link.clone(), Box::new(move |mut update_node, mut child_links| {
-                    let child_link = match child_links.pop() {
-                        Some(link) => Ok(link),
-                        None => Err(QueryGraphBuilderError::RecordNotFound(format!(
-                            "No '{child_model_name}' record (needed to update inlined relation on '{parent_model_name}') was found for a nested upsert on relation '{relation_name}'."
-                        ))),
-                    }?;
+                QueryGraphDependency::ProjectedDataDependency(
+                    child_link.clone(),
+                    Box::new(move |mut update_node, mut child_links| {
+                        let child_link = child_links.pop().expect("child link should be present");
 
-                    if let Node::Query(Query::Write(ref mut wq)) = update_node {
-                        wq.inject_result_into_args(parent_link.assimilate(child_link)?);
path: query-engine/core/src/query_graph_builder/write/nested/upsert_nested.rs
user: login: jacek-prisma
body: just removing code that's no longer needed
created_at: 2025-05-06T14:47:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -95,17 +95,9 @@ pub fn nested_update(
         graph.create_edge(
             &find_child_records_node,
             &update_node,
-            QueryGraphDependency::ProjectedDataDependency(
+            QueryGraphDependency::ProjectedDataSinkDependency(
                 child_model_identifier.clone(),
-                Box::new(move |mut update_node, mut child_ids| {
-                    let child_id = child_ids.pop().expect("child id should be present");
-
-                    if let Node::Query(Query::Write(WriteQuery::UpdateRecord(ref mut ur))) = update_node {
-                        ur.set_selectors(vec![child_id]);
-                    }
-
-                    Ok(update_node)
-                }),
+                DataSink::SingleRowArray(&UpdateRecordFilterInput),
path: query-engine/core/src/query_graph_builder/write/nested/update_nested.rs
user: login: jacek-prisma
body: integrates the new trait
created_at: 2025-05-06T14:50:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -159,16 +151,9 @@ pub fn nested_update_many(
         graph.create_edge(
             &find_child_records_node,
             &update_many_node,
-            QueryGraphDependency::ProjectedDataDependency(
+            QueryGraphDependency::ProjectedDataSinkDependency(
                 child_model_identifier.clone(),
-                Box::new(move |mut update_many_node, child_ids| {
-                    if let Node::Query(Query::Write(WriteQuery::UpdateManyRecords(ref mut ur))) = update_many_node {
-                        // ur.set_filter(Filter::and(vec![ur.filter.clone(), child_ids.filter()]));
-                        ur.record_filter = child_ids.into();
-                    }
-
-                    Ok(update_many_node)
-                }),
+                DataSink::AllRows(&UpdateManyRecordsFilterInput),
path: query-engine/core/src/query_graph_builder/write/nested/update_nested.rs
user: login: jacek-prisma
body: integrates the new trait
created_at: 2025-05-06T15:22:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -118,6 +118,16 @@ impl Display for QueryGraphDependency {
                         .collect::<Vec<_>>()
                 )
             }
+            Self::ProjectedDataSinkDependency(selection, _, _) => {
+                write!(
+                    f,
+                    "StaticProjectedDataDependency ({:?})",
path: query-engine/core/src/query_graph/formatters.rs
user: login: aqrln
body: ```suggestion
                    "ProjectedDataSinkDependency ({:?})",
```
created_at: 2025-05-06T18:30:13Z
author_association: MEMBER

PR comments:
user: login: codspeed-hq[bot]
created_at: 2025-05-06T14:50:19Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/prisma/prisma-engines/branches/feat%2Fprojected-data-sink-dependency)

### Merging #5381 will **not alter performance**

<sub>Comparing <code>feat/projected-data-sink-dependency</code> (01bdbd0) with <code>main</code> (b8a67b3)</sub>



### Summary

`✅ 11` untouched benchmarks  




author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-06T14:52:03Z
body: <!-- wasm-size -->
### WASM Query Engine file Size

|Engine            | This PR                                      | Base branch                                      | Diff
|------------------|----------------------------------------------|--------------------------------------------------|-----------------------------------------------
| Postgres         | 2.185MiB    |  2.185MiB    | 375.000B
| Postgres (gzip)  | 876.772KiB |  877.063KiB | -298.000B
| Mysql            | 2.151MiB         |  2.150MiB          | 374.000B
| Mysql (gzip)     | 862.310KiB      |  862.466KiB       | -160.000B
| Sqlite           | 2.059MiB        |  2.059MiB         | 369.000B
| Sqlite (gzip)    | 826.109KiB     |  825.959KiB     | 153.000B
author_association: CONTRIBUTOR