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

Support recursive relationships #3725

Open
Tracked by #5
ridsameer opened this issue Sep 21, 2020 · 54 comments
Open
Tracked by #5

Support recursive relationships #3725

ridsameer opened this issue Sep 21, 2020 · 54 comments
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. kind/feature A request for a new feature. topic: database-functionality topic: postgresql topic: tree

Comments

@ridsameer
Copy link

Problem

Let's say I have a table called categories. This table can refer to itself as a foreign key with parentCategory.
So a categories can have many subcategories, and subcategories can also have subcategories, with an unlimited combination. The idea is to support recursive queries to easily query them along with their sub categories.

Suggested solution

Some valid implementation of something similar to Postgres WITH RECURSIVE
https://www.postgresqltutorial.com/postgresql-recursive-query/

Alternatives

N/A

Additional context

N/A

@sbrichardson
Copy link

For data structures like this, The best way I've found to handle with GraphQl in general (unrelated to prisma) is to flatten the tree to one level, and track the parentId/index etc as properties. Then have a simple/fast recursive function that builds the tree or converts back to a flat list etc.

This is more of a Graphql limitation (not supporting recursion) but I've found working with a flattened list vs (n)tree has other benefits, like being able to mutate the tree as a linked list/doubly linked list etc).

@pantharshit00 pantharshit00 added kind/feature A request for a new feature. team/product labels Sep 22, 2020
@pantharshit00
Copy link
Contributor

Self referencing relations are possible in the Prisma schema today. See docs on them here: https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#self-relations

That can get you started. But in general thanks for opening this issue for WITH RECURSIVE CTEs. This can improve experience here.

For now you can also use a raw query here: https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/raw-database-access

@ridsameer
Copy link
Author

Thanks @pantharshit00 . Thankfully this isn't required in my project right now but is in a roadmap for another personal project so thought I'd bring it up.

Thanks again

@pantharshit00 pantharshit00 added the domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. label Mar 2, 2021
@masihjahangiri
Copy link

masihjahangiri commented Aug 1, 2021

I have used raw queries for recursive queries.
I Think It's simple to implement in Prisma and it's very common in every project.

@danielolaviobr
Copy link

Couldn't a possible solution be having a decorator in our model saying that a certain relation is included by default when we query that table? So that when we would query for the tree structure, we would only query for the rows that have the parentId as null and the children would come by default, even in the children's children.

@lumenwrites
Copy link

lumenwrites commented Nov 25, 2021

Hey guys, I'm struggling with the same issue, and would really like to see this feature. Someone on StackOverflow suggested that I leave a comment here to help the developers track the demand for this feature.

Meanwhile, can someone please help me to figure out how to write a raw query that accomplishes the same thing? I'm not too great at SQL, and I'm relatively new to Prisma, and I'd really love to find a solution to this.

@brielov
Copy link

brielov commented Nov 30, 2021

I've been waiting for this feature too. It would be awesome to see this feature gain some traction.

@janpio janpio added domain/schema Issue in the "Schema" domain: Prisma Schema, Introspection, Migrations etc. domain/psl Issue in the "PSL" domain: Prisma Schema Language topic: tree topic: postgresql and removed domain/schema Issue in the "Schema" domain: Prisma Schema, Introspection, Migrations etc. domain/psl Issue in the "PSL" domain: Prisma Schema Language labels Feb 6, 2022
@Southclaws
Copy link

If the Prisma team is looking for a real-world example then I have one!

Here's a schema with a Post model which has repies and also a root post. The reason it's done this way is so there doesn't need to be a separate model for "threads" and "posts" it's all just a "post" and "first" indicates if it's the first post in a thread.

And here's a recursive CTE used to count the number of replies to threads (posts with "first" set to true)

If this could be expressed purely via Prisma, that would be awesome! I already hate the complexity and lack of composability with SQL syntax, but recursive CTEs are a special kind of hell!

@JaapWeijland
Copy link

+1

@sbrichardson
Copy link

sbrichardson commented Mar 25, 2022

For data structures like this, The best way I've found to handle with GraphQl in general (unrelated to prisma) is to flatten the tree to one level, and track the parentId/index etc as properties. Then have a simple/fast recursive function that builds the tree or converts back to a flat list etc.

This is more of a Graphql limitation (not supporting recursion) but I've found working with a flattened list vs (n)tree has other benefits, like being able to mutate the tree as a linked list/doubly linked list etc).

This is definitely the best way to handle, in my opinion. You can refold the items on the client or server with a simple recursive for loop.

This (incomplete?) example may have some unnecessary steps for your need, but it's taken from code that takes an ordered, flat array of React/DOM components (json) and builds a correct DOM/component tree using a parent id stored on each component. It could also be adapted to track an index property, if the array needed to be stored unordered.

const children = []
const treeDict = {}


const childrenNested = generateTree(children, treeDict)


function generateTree(ch, dict) {
  const tree = []
  for (
    let i = 0, children, parent, node, pid, id, n = ch.length;
    i < n;
    ++i
  ) {
    node = ch[i]
    id = node.id
    pid = node.pid
    if (pid === null || !(typeof pid === 'string' && pid.length > 0)) {
      tree[tree.length] = dict[id]
      continue
    }
    parent = dict[pid]
    children = parent.children
    if (children === null || children === undefined) {
      parent.children = [dict[id]]
      continue
    }
    children[children.length] = dict[id]
  }
  return tree
}

@Jackman3005
Copy link

We also have recursive relationships in our database, similar to nodes having potentially one parent and potentially many children. As we grow features to process data across nodes at depth it's becoming a performance hit to use Prisma because we need to continuously query, check for more nodes and query each of those again, etc.

Is there any plan for this to be on the roadmap at any point in the next 6 months?

Thanks in advance!

@nac62116
Copy link

nac62116 commented Sep 7, 2022

+1

@andrewmclagan
Copy link

Bumping this as the issue was opened a year ago. For what is a pretty basic feature I would expect more traction from the Prisma team?

@SooOverpowered
Copy link

Faced similar situation like this. Ended up writing a helper function just to build up the category object. Probably wasting performance on querying but it solves the problem.

Here is the sample code. The data structure goes as follow: Project -> Category -> Subcategory -> subsub... you get the gist of it. The include: {document: true} is just additional data that I want to include for the query.

import prisma from '../../../lib/prisma'

async function GetSubCategory(arr) {
    const new_arr = []
    for (const element of arr) {
        const subCategories = await prisma.category.findMany({
            where: {
                parentCategoryId: element.id
            },
            include: {
                documents: true
            }
        })
        if (!Array.isArray(subCategories) || !subCategories.length) {
            // array does not exist, is not an array, or is empty
            // ⇒ do not attempt to process array
            new_arr.push(element)
        } else {
            const temp_arr = await GetSubCategory(subCategories)
            element.subCategories = temp_arr
            new_arr.push(element)
        }
    }
    return new_arr
}

export default async function handler(req, res) {
    const session = await unstable_getServerSession(req, res, authOptions)
    const project = await prisma.user.findUnique({
        where: {
            email: session.user.email
        }
    }).projects({
        where: {
            id: req.query.pid
        },
        include: {
            categories: {
                include: {
                    documents: true,
                }
            }
        }
    })
    project[0].categories = await GetSubCategory(project[0].categories)

    if (!project[0]) {
        res.status(404).json({ message: "Project not found." });
        return;
    }
    return res.json(project[0])
}

@suraj5969
Copy link

+1

@Alex-ray
Copy link

Alex-ray commented Jan 13, 2023

+1

I'm surprised recursive querying for self referencing models aren't support for Prisma. Even simple features like nested folders is commonly expected for modern applications. Given there is underlying DB support for this, I'm curious why this hasn't been prioritized?

@abedaarabi
Copy link

I tried to use your scheme as an example, but unfortunately I got an error: :/

Error validating: A self-relation must have onDelete and onUpdate referential actions set to NoAction in one of the @relation attributes. (Implicit default onDelete: SetNull, and onUpdate: Cascade) Read more at https://pris.ly/d/cyclic-referential-actions

I needed up doing it that why: ofc i can't query the depth of each subfolder :/

model Folder {
id String @id @unique @default(uuid())
createdAt DateTime @default(now())

name String
isFolder Boolean @default(true)
childId String
}

@stepaniukm
Copy link

+1 Waiting for this!

@mathantunes

This comment was marked as abuse.

@jonknyc
Copy link

jonknyc commented Apr 1, 2023

You can use a recursive function

private recursive(level: number) {
    if (level === 0) {
      return {
        include: {
         categories: true
        }
      };
    }
    return {
      include: {
        categories: this.recursive(level - 1)
      }
    };
  }

after call it at

this.prisma.category.findUnique({
   where: {id},
   include: {
       categories: this.recursive(10),
    }
});

However, this will create a recursion with a known number of floors

I would propose something like a special "recurse" option on the include interface. Ideally it could be "true" for infinite recursion, or a number if I want to specify a max recursion depth.

For example:

Infinite recursion:

this.prisma.category.findUnique({
  where: {id},
  include: {
    categories: {
      recurse: true // recurse infinitely
    }
  }
})

Max depth:

this.prisma.category.findUnique({
  where: {id},
  include: {
    categories: {
      recurse: 10 // max of 10 levels deep
    }
  }
})

This feels like it shouldn't be too hard to create, and it would be such a great feature.

@antoine13330
Copy link

Any Info about a potential release of the feature ? Kind of needed and a big time saver too. One of the main reason to use ORM too 🤔 ( however everything is incredible down here thanks a lot prisma team ❤️ )

@jedwards1211
Copy link

jedwards1211 commented May 15, 2023

@sbrichardson

For data structures like this, The best way I've found to handle with GraphQl in general (unrelated to prisma) is to flatten the tree to one level, and track the parentId/index etc as properties. Then have a simple/fast recursive function that builds the tree or converts back to a flat list etc.

This is orthogonal to the need for recursive queries...you'll get flat output from the database and reconstruct the tree in JS regardless, but if the table is large you can't afford to fetch everything, you have to select only the rows you want and then use a recursive query to fetch their ancestors and/or descendants, and the tree-building function doesn't help with that

@AlexanderHott
Copy link

Closing in on 3 years, is this on any roadmap?

@Umbrien
Copy link

Umbrien commented Jun 19, 2023

Closing in on 3 years, is this on any roadmap?

@AlexanderHott here is Prisma roadmap and sadly there is no mention of this feature as long as no feedback from the team here too

@TamirCode
Copy link

TamirCode commented Jun 23, 2023

I also have the posts -> comments -> comments use case. But another use case I have is for the permissions system I am building. Each roles will have a set of permissions, but they can also inherit permissions of other roles, like in this example (imagine foreign keys instead of strings):

const roles = [
	{
		name: "user",
		permissions: ["services.read"]
	},
	{
		name: "moderator",
		permissions: ["services.create"],
		inherit: ["user"]
	},
	{
		name: "admin",
		permissions: ["services.edit", "services.delete"],
		inherit: ["moderator"]
	},
]

If a user has role admin, I would need a recursive query and then i will gather all his permissions with javascript

// prisma returned object:
const exampleUser = {
	username: "blah",
	permissions: ["random.read", "random.create", "random.edit"], // specific permissions to user
	permissionsDenied: ["services.delete"], // negate permissions (highest precedence)
	roles: [
		{
			name: "admin",
			permissions: ["services.edit", "services.delete"],
			inherit: [
				{
					name: "moderator",
					permissions: ["services.create"],
					inherit: [
						{
							name: "user",
							permissions: ["services.read"]
						}
					]
				}
			]
		}
	]
}

and hopefully it would take care of edge cases like having circular reference would stop the recursion

@TamirCode
Copy link

possible syntax for above

const user = await prisma.user.findUnique({
	where: {
		id: 99,
	},
	include: {
		roles: {
			recursiveInclude: { 
				inherit: true
			}
			// ^ same as include { inherit: { include: inherit ...}}
		}
	}
})

@mbilaldnc
Copy link

Dear Prisma developers, I don't like writing comments like "+1" but please do not ignore this feature request. I've been searching this topic occasionally for about a year incase I missed an issue related to it.

@dugan-dev

This comment was marked as off-topic.

@rafaelklaessen
Copy link

Are there any updates on this? The proposed recurse: true syntax seems great -- has this issue been added to a roadmap?

@Jackman3005
Copy link

For us we not only would want to select/include relationships recursively, but would need the ability to do wheres recursively as well. In our model, you may be assigned to a "Quest". A Quest can have "SubQuests" which creates the recursion. If you are assigned to the top Quest, you should be considered assigned to all subquests below. When checking which permissions a user has for a particular subquest, we need to be able to recurse through all Quests above it until reaching the root to determine if they have been assigned or not.

We are hacking this with a hard-coded "depth" parameter for now, but it's not very performant and breaks when the depth goes beyond that.

@1216892614
Copy link

Will this been add in Roadmap? Please do not ignore this feature request!

@janpio
Copy link
Member

janpio commented Aug 25, 2023

Yes, one day. Right now we are busy with other things. We only have limited capacity, so can not just add all the things we find useful to a roadmap - otherwise none of them will get done.

@Neosoulink
Copy link

Nah... We need this recurse: true 😅

@antoine13330
Copy link

Nah... We need this recurse: true 😅

Yes that's kinda of shame that we don't have it...

@justin-hackin
Copy link

You can use a recursive function

private recursive(level: number) {
    if (level === 0) {
      return {
        include: {
         categories: true
        }
      };
    }
    return {
      include: {
        categories: this.recursive(level - 1)
      }
    };
  }

after call it at

this.prisma.category.findUnique({
   where: {id},
   include: {
       categories: this.recursive(10),
    }
});

However, this will create a recursion with a known number of floors

As I suspected, this messes up the TS types for the model but I used it to generate the unwieldy object for the query.

@rafaelklaessen
Copy link

You can use a recursive function

private recursive(level: number) {
    if (level === 0) {
      return {
        include: {
         categories: true
        }
      };
    }
    return {
      include: {
        categories: this.recursive(level - 1)
      }
    };
  }

after call it at

this.prisma.category.findUnique({
   where: {id},
   include: {
       categories: this.recursive(10),
    }
});

However, this will create a recursion with a known number of floors

As I suspected, this messes up the TS types for the model but I used it to generate the unwieldy object for the query.

Wouldn't you be better of simply getting all categories and building the hierarchical structure post-retrieval? Would be more performant, and you could have a variable number of nesting levels

@Neosoulink
Copy link

Effectively, another challenge when using Prisma helpers is to keep your Typescript able to follow your logic

On my side I created a logic able to do that, to create a recursive function and keep my typing updated

Here is my solution:

export const getVotes = (auth?: SignedInAuthObject | SignedOutAuthObject) => {
  return {
    ...(auth?.userId
      ? {
          upVotes: { where: { userId: auth.userId } },
          downVotes: { where: { userId: auth.userId } },
        }
      : {}),
    _count: { select: { upVotes: true, downVotes: true } },
  };
};

// ...

// I created a recursive function and defined the return type

export interface RecursivePost extends ReturnType<typeof getVotes> {
  comments?: {
    orderBy: { createdAt: "desc" | "asc" };
    include: RecursivePost | ReturnType<typeof getVotes>;
  };
}

export const generateNestedComments = (
  depth: number,
  auth?: SignedInAuthObject | SignedOutAuthObject,
): RecursivePost => {
  if (depth === 0) return getVotes(auth);

  const include = generateNestedComments(depth - 1, auth);

  return {
    ...getVotes(auth),
    comments: {
      orderBy: { createdAt: "desc" },
      include,
    },
  };
};

💡 In my shared code I'm getting a Post and a nested list of comments on that post, and because I was drunk and lazy I used the same Model for Post and Comment. But, the most important thing here is the fact that I'm able to set a depth to my function and keep my Typescript able to follow the logic.

Here are the code source links:

Agreed, my code is not the best one and it would be much easier with a built-in solution, but still, my solution helped me with this Reddit-clone app, (here's the link https://reddit-clone-nine-xi.vercel.app/)

@justin-hackin
Copy link

justin-hackin commented Apr 15, 2024

You can use a recursive function

private recursive(level: number) {
    if (level === 0) {
      return {
        include: {
         categories: true
        }
      };
    }
    return {
      include: {
        categories: this.recursive(level - 1)
      }
    };
  }

after call it at

this.prisma.category.findUnique({
   where: {id},
   include: {
       categories: this.recursive(10),
    }
});

However, this will create a recursion with a known number of floors

As I suspected, this messes up the TS types for the model but I used it to generate the unwieldy object for the query.

Wouldn't you be better of simply getting all categories and building the hierarchical structure post-retrieval? Would be more performant, and you could have a variable number of nesting levels

Getting all the records is not expensive if say, there's a global category tree and all users need the whole tree. However, it might be more complicated than that and you could end up with lots of irrelevant categories.

This gave me an 💡: you can limit access to records with Zenstack so that you only get the relevant categories. This could be really useful if say a category could exist in multiple trees via a many-to-many relationship. However, Zenstack doesn't work with recursion either so I ended up with some ugly rules to deal with recursion e.g. :

@@allow('all',
    regions?[admins?[user == auth()]] ||
    parentCalendarGroup.regions?[admins?[user == auth()]] ||
    parentCalendarGroup.parentCalendarGroup.regions?[admins?[user == auth()]] ||
    parentCalendarGroup.parentCalendarGroup.parentCalendarGroup.regions?[admins?[user == auth()]]
)

There's a somewhat related issue here: zenstackhq/zenstack#276

@Aloysius999
Copy link

Is there an update to this feature request?
It's been nearly 4 years since the original post.
A recursive feature is required for my project - my example is a folder tree for a messaging system.
Thanks

@saidbenmoumen
Copy link

+1

@antoine13330
Copy link

antoine13330 commented Jul 2, 2024

Is there an update to this feature request? It's been nearly 4 years since the original post. A recursive feature is required for my project - my example is a folder tree for a messaging system. Thanks

Idk if u made it but u can try to queryRaw and make the recursive query yourself if really needed.

see here

PS : saw after writing that people already said it but just pinging u trying to help :)

BUT PLEASE PRISMA TEAM DO IIIIIIITTTTTTT ( still love ya )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. kind/feature A request for a new feature. topic: database-functionality topic: postgresql topic: tree
Projects
None yet
Development

No branches or pull requests