paljs / prisma-tools

Prisma tools to help you generate CRUD system for GraphQL servers
https://paljs.com
MIT License
683 stars 54 forks source link

Self relation causes "RangeError: Maximum call stack size exceeded" ? #173

Closed gavrichards closed 3 years ago

gavrichards commented 3 years ago

I had PrismaDelete working well, until I introduced a self relation in my schema.

Now even when trying to delete records which don't make use of the self relation, it fails and gives this error:

(node:15814) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
    at _cycle (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:520:33)
    at _forOf (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:529:5)
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:697:20
    at _temp7 (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:714:10)
    at PrismaDelete.getDeleteArray (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:740:76)
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:705:49
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:709:16
    at _cycle (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:502:20)
    at _forOf (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:529:5)
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:697:20
    at _temp7 (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:714:10)
    at PrismaDelete.getDeleteArray (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:740:76)
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:705:49
    at /Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:709:16
    at _cycle (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:502:20)
    at _forOf (/Users/[redacted]/node_modules/@paljs/plugins/dist/plugins.cjs.development.js:529:5)

Does PrismaDelete support self relations? Here's an example of the model in my schema:

model Slot {
  id                    Int         @default(autoincrement()) @id
  slug                  String?
  type                  SlotType
  parentSlotId          Int?
  parentSlot            Slot?       @relation("SlotParentChildren", fields: [parentSlotId], references: [id])
  /// @onDelete(CASCADE)
  childSlots            Slot[]      @relation("SlotParentChildren")
}

I'm invoking it like this:

const where = {
  id: parseInt(slotId),
};

await prisma.onDelete({model: 'Slot', where});

slot = await prisma.slot.delete({
  where,
});

Thanks for your help.

AhmedElywa commented 3 years ago

You can't use our plugin for this relationship because when I will look for relation I will see it has children and children can have children so it's an infinite loop

You can remove the comment in your model and delete the parent-children manual first and then delete the parent

gavrichards commented 3 years ago

Fair enough. It would be good if there was a way of specifying a loop depth, so that this could work but only to the degree specified.

AhmedElywa commented 3 years ago

I will think on your suggestions

gavrichards commented 3 years ago

Thanks @AhmedElywa, I appreciate it.

AhmedElywa commented 3 years ago

With the last version, 2.12.0, you can use self-relation, and our plugin will delete just one nested relation. so if the record has children, I will delete his children, but if children have children, I will face an error

gavrichards commented 3 years ago

@AhmedElywa Does this change apply to all relations, not just self-relation? I am now finding a cascading delete (not a self-relation) isn't working.

AhmedElywa commented 3 years ago

Can you please give me the error message I tested the plugin with normal relationships and working correctly

gavrichards commented 3 years ago

I think the issue might be when you try to delete something that has a self-relation, and that linked item has cascading deletes. The error I'm receiving is from Prisma, not your library. It's this one:

The change you are trying to make would violate the required relation 'AToB' between the A and B models.

AhmedElywa commented 3 years ago

can you please give me example models and the prisma query you do

gavrichards commented 3 years ago

Sure, so here is a simpler version of what I have - I've removed irrelevant stuff from the model.

model Slot {
  id                    Int         @default(autoincrement()) @id
  parentSlotId          Int?
  parentSlot            Slot?       @relation("SlotParentChildren", fields: [parentSlotId], references: [id])
  /// @onDelete(CASCADE)
  childSlots            Slot[]      @relation("SlotParentChildren")
  /// @onDelete(CASCADE)
  ruleGroups            RuleGroup[]
}

model RuleGroup {
  id                Int         @default(autoincrement()) @id
  slot              Slot        @relation(fields: [slotId], references: [id])
  slotId            Int
  /// @onDelete(CASCADE)
  rules             Rule[]
}

model Rule {
   id               Int         @default(autoincrement()) @id
   ruleGroup        RuleGroup   @relation(fields: [ruleGroupId], references: [id])
   ruleGroupId      Int
   type             RuleType
   condition        RuleCondition
   value            String
}

As you can see, a Slot can have child Slots. A Slot (or a child Slot) can then have Rule Groups. Rule Groups have Rules.

Say I have a Slot, which has multiple child Slots, and one of those has some Rule Groups.

The query I'm running is:

const where = {
  id: parseInt(slotId),
};

await prisma.onDelete({model: 'Slot', where});

slot = await prisma.slot.delete({
  where,
});

I then get:

PrismaClientKnownRequestError2 [PrismaClientKnownRequestError]: 
Invalid `prisma.slot.deleteMany()` invocation:

  The change you are trying to make would violate the required relation 'RuleGroupToSlot' between the `RuleGroup` and `Slot` models.
    at PrismaClientFetcher.request (webpack-internal:///./prisma/client/runtime/index.js:1009:3183)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  code: 'P2014',
  clientVersion: '2.16.0',
  meta: {
    relation_name: 'RuleGroupToSlot',
    model_a_name: 'RuleGroup',
    model_b_name: 'Slot'
  }
}

My temporary fix is to first delete child slots - the cascading delete for rule groups then works.

// First delete any slots which have the requested slot as their parent
const whereParent = {
  parentSlotId: parseInt(slotId),
};

await prisma.onDelete({model: 'Slot', where: whereParent});

await prisma.slot.deleteMany({
  where: whereParent,
});

// Now delete this slot
const where = {
  id: parseInt(slotId),
};

await prisma.onDelete({model: 'Slot', where});

slot = await prisma.slot.delete({
  where,
});
AhmedElywa commented 3 years ago

What I do now delete the children but not logged into the children to delete their relation. If I do this, I will have an infinity loop

AhmedElywa commented 3 years ago

Can you do it like this will be easier?

model Slot {
  id                    Int         @default(autoincrement()) @id
  /// @onDelete(CASCADE)
  childSlots            ChildSlot[]      @relation("SlotParentChildren")
  /// @onDelete(CASCADE)
  ruleGroups            RuleGroup[]
}

model ChildSlot {
  id                    Int         @default(autoincrement()) @id
  parentSlotId          Int?
  parentSlot            Slot?       @relation("SlotParentChildren", fields: [parentSlotId], references: [id])
  /// @onDelete(CASCADE)
  ruleGroups            RuleGroup[]
}

model RuleGroup {
  id                Int         @default(autoincrement()) @id
  slot              Slot        @relation(fields: [slotId], references: [id])
  slotId            Int
 childSlot              Slot        @relation(fields: [childSlotId], references: [id])
  childSlotId            Int
  /// @onDelete(CASCADE)
  rules             Rule[]
}

model Rule {
   id               Int         @default(autoincrement()) @id
   ruleGroup        RuleGroup   @relation(fields: [ruleGroupId], references: [id])
   ruleGroupId      Int
   type             RuleType
   condition        RuleCondition
   value            String
}
gavrichards commented 3 years ago

Possibly, it's just not as clean in RuleGroup as both slot and childSlot will have to be optional properties meaning I can't be sure there will be a parent. I'm not even sure if the cascade relationship will work like this.