Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: regression on 1-1 nested connect in create #3514

Merged
merged 8 commits into from Dec 20, 2022

Conversation

Weakky
Copy link
Member

@Weakky Weakky commented Dec 15, 2022

Overview

fixes prisma/prisma#16743
fixes prisma/prisma#16843

This PR fixes a regression introduced by #3443. Specifically, when a nested connect is in a create, we should always attempt to disconnect the already connected record so that the create can succeed.

A couple more things were done:

  • This edge-case was already tested. Unfortunately, the relation_link_test macro always inlined the relation on the child side for optional 1-1 relation. This is now fixed, hence why the PR doesn't add another test. This test lives here
  • The fix above uncovered another bug recently introduced with extendedWhereUnique. Note that it's not fixed for MongoDB because the solution might require us to remove it entirely from the api. An issue was created here: Nested disconnect fails on MongoDB with extendedWhereUnique prisma#16869

Comment on lines +137 to +139
Identifier::Simple if on_child.is_to_one_opt() && on_parent.is_to_one_opt() => {
vec![RelationReference::SimpleParentId(on_child), RelationReference::NoRef]
}
Copy link
Member Author

@Weakky Weakky Dec 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sure we generate optional 1-1 relation inlined on both sides (child and parent).

It's a bit hard to follow what this does without the full context. In a nutshell, RelationReference::NoRef means some of the generated datamodels will not be inlined on the child side. Therefore, the logic for the parent side will inline the relation when it's not already inlined in the child.

This means we can expect generated datamodels of these (rough) shapes:

(inlined child-side)

model Parent {
  id Int @id
  childOpt Child?
}

model Child {
  id Int @id
  parentId Int? @unique
  parentOpt? @relation(fields: [parentId], references: [id])
}

(inlined parent-side)

model Parent {
  id Int @id

  childId Int? @unique
  childOpt Child? @relation(fields: [childId], references: [id])
}

model Child {
  id Int @id
  parentOpt?
}

Comment on lines +359 to +363
if parent_is_create {
handle_one_to_one_parent_create(graph, parent_node, parent_relation_field, filter, child_model)
} else {
handle_one_to_one_parent_update(graph, parent_node, parent_relation_field, filter, child_model)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff of the split is not super easy to read. This code mainly removed most of the is_parent_create checks and inlined everything in separate functions.

It made it easier to only do the idempotency check on nested connect inside update.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easier to read than before, so 👍🏾

@Weakky Weakky added this to the 4.8.0 milestone Dec 15, 2022
Comment on lines 174 to 182
// If the relation is inlined on the parent and a filter is applied on the child, we need to join to apply this filter
let filter = if !filter.is_empty() {
parent_relation_field.to_one_related(filter)
} else {
filter
};

(parent_node, parent_model, extractor_model_id, null_record_id)
(parent_node, parent_model, extractor_model_id, null_record_id, filter)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix for the nested disconnect bug. When the relation is inlined on the parent and a filter is applied on the child, we need to convert the filter to a relation filter so that a join is rendered. eg:

model Parent {
  id Int @id

  childId Int? @unique
  childOpt Child? @relation(fields: [childId], references: [id])
}

model Child {
  id Int @id
  non_unique Int
  parentOpt?
}
await prisma.parent.update({
  where: { id: 1 },
  data: {
    childOpt: { disconnect: { non_unique: 1 } }
  }
})

This should roughly render:

UPDATE Parent SET childId = NULL
LEFT JOIN Child c ON (c.id = Parent.childId)
WHERE Child.non_unique = 1

Note: We do not use joins for relational filters. This some pseudo code.

@Weakky Weakky force-pushed the fix/1-1-nested-connect-in-create branch from 9002093 to be5934c Compare December 16, 2022 10:50
Copy link
Collaborator

@miguelff miguelff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look correct, and inline comments helped while reviewing the code. The code in the query graph builder and schema generation for this diff is not trivial.

Comment on lines +359 to +363
if parent_is_create {
handle_one_to_one_parent_create(graph, parent_node, parent_relation_field, filter, child_model)
} else {
handle_one_to_one_parent_update(graph, parent_node, parent_relation_field, filter, child_model)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easier to read than before, so 👍🏾

if parent_relation_field.is_inlined_on_enclosing_model() {
// Inlined on parent
let parent_model = parent_relation_field.model();
let extractor_model_id = parent_model.primary_identifier();
let null_record_id = SelectionResult::from(&parent_relation_field.linking_fields());
// If the relation is inlined on the parent and a filter is applied on the child then it means the update will be done on the parent table.
// Therefore, the filter applied on the child needs to be converted to a "relational" filter so that the connector renders the adequate SQL to join the Child table.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏾

@Weakky Weakky merged commit d6e67a8 into main Dec 20, 2022
@Weakky Weakky deleted the fix/1-1-nested-connect-in-create branch December 20, 2022 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants