question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Tree structures support

See original GitHub issue

Problem

Hello! There is no real functionality for working with tree structures recursively.

It would be awesome if there was a functionality added to define a model & query the most popular hierarchal data structures for relational databases. But for the sake of simplicity and referential integrity, i think the adjacency list support would be the the best choice for the first solid implementation.

Suggested solution

The first thing that comes to my mind is that there needs to be some kind of way to declare that a model inside a schema wants to be a tree node.

Simple example:

model Category TreeNode { 
  // Or: Tree, TreeNode('adjacencyList'), Tree('adjancencyList')
  id    Int          @id @default(autoincrement())
  name  String

  // Those fields below could be added behind the scenes based on model declaration above
  // But that would be dependent on experimental schema migration tool, so idk
  // Anyways, in the case of basic adjacency list, those could be required
  children  Category[]   @relation("CategoryToCategory")
  parent    Category?    @relation("CategoryToCategory", fields: [parentId], references: [id])
  parentId  Int?
}

Then there should be some methods for working with trees. Some basic ones, as an example:

  • getDescendants
  • getAncestors
  • getChildren
  • getRoot or getRoots
  • etc…

Some of the methods would use recursive CTE queries in the case of adjacency list, and other algorithms in case of other tree structures, if implemented.

Then it would work like this:

const subCategories = await prisma.category
  .findUnique({
    where: { id: 5 }
  })
  .getDescendants({
    includeSelf: true // Optional, and maybe default as false? Just an idea.
    // ...and other useful properties, depending on the method...
  })

// Or

const subCategories = await prisma.category
  .getDescendants({
    where: { id: 5 }
  })

Additional context

Here are some design ideas: https://typeorm.io/#/tree-entities https://github.com/django-treebeard/django-treebeard https://django-mptt.readthedocs.io/en/latest/models.html#

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:92
  • Comments:12 (1 by maintainers)

github_iconTop GitHub Comments

79reactions
sorenbscommented, Jun 14, 2021

Context

Related issues: #3725 and #3643

A popular application that uses this pattern is Notion.

Prisma already supports the data structure required for modeling a tree, known as one-to-many self relation.

What is missing is better query support. I don’t believe we need to introduce one or more special model types in order to achieve this.

Problem

The schema

model Category { 
  id    Int          @id @default(autoincrement())
  name  String

  children  Category[]   @relation("SubCategories")
  parent    Category?    @relation("SubCategories", fields: [parentId], references: [id])
  parentId  Int?
}

currently supports queries like this to load the children of a category. You can extend this to load as many levels of children you want, and it will be returned in a correctly typed object structure:

  // get one level of children
  const withChildren1 = await prisma.category.findUnique({where:{id: 1}, include: {children: true}})
  // get two levels of children
  const withChildren2 = await prisma.category.findUnique({where:{id: 1}, include: {children: {include: {children: true}}}})

  withChildren1?.children[0].id
  withChildren2?.children[0].children[0].id

And similarly to retrieve parents:

  // get one level of parent
  const withParent1 = await prisma.category.findUnique({where:{id: 1}, include: {parent: true}})
  // get two level of parents
  const withParent2 = await prisma.category.findUnique({where:{id: 1}, include: {parent: {include: {parent: true}}}})

  withParent1?.parent?.id
  withParent2?.parent?.parent?.id

While this works, you must specify a maximum depth, and you have to do it in this rather clunky way. It would be nice if you could perform these same queries without specifying a maximum depth, and optionally specify a maximum depth simply by providing an integer.

Suggestion

For models with self relations, we could introduce a new includeRecursive query:

  // get all levels of children
  const withAllChildren = await prisma.category.findUnique({where:{id: 1}, includeRecursive: {children: true}})

If the Category with id 1 is the root, this query will load the entire tree.

Similarly, if you need to load a category, and the id of all parent categories all the way up to the root, you could write this query:

const withAllParentIds = await prisma.category.findUnique({where:{id: 1}, includeRecursive: {parent: {select: {id: true}}}})

A consideration

This query will return a structure like the following:

{
  id: 1,
  name: "the category"
  parent: {
    id: 2,
    parent: {
      id: 3
    }
  }
}

This is conceptually easy to understand, and fits the mental model that Prisma generally uses, but can be a bit unwieldy. Perhaps we could introduce a convenience method to flatten the recursive relation:

const withAllParentIds = await prisma.category.findUnique({where:{id: 1}, includeRecursive: {parent: {select: {id: true}}}}).withFlattenedRecursiveRelations()

// returns
{
  id: 1,
  name: "the category"
  parent: [{id: 2}, {id: 3}]
}

Implementation

This could use the same multiple-roundtrips implementation that our current include: {children: true} query does. I believe this is the strategy Notion is using to load nested blocks on a page.

Alternatively, we could use the recursive CTE approach mentioned in the parent. It looks like all common SQL servers (and definitely MySQL, Postgres, MSSQL and SQLite) support recursive CTEs.

Either way, we would need to take care not to end up in an infinite loop, either by enforcing a maximum depth or employing a strategy like the one described here. Max depth could be exposed in the API like this:

  // get all levels of children
  const withAllChildren = await prisma.category.findUnique({where:{id: 1}, includeRecursive: {children: {maxDepth: 10}}})

Further work

  • There might be other queries relevant to tree structures that we can implement for models with self relations.
  • There might be specific patterns for manipulating data in a tree structure. We should make sure that they are properly supported by our current nested mutations or introduce specific support for models with self relations.
42reactions
johnkm516commented, Sep 13, 2022

It would be great if there was official Prisma support for this. Are there any plans on the roadmap for this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Tree Support Systems | Tree Care Kit
Tree support systems help support the tree by limiting the movement of branches, leaders or the entire tree. This reduces the risk of...
Read more >
Types of tree house support
The choice is simple - fixed joints for connections between trees that don't move much and flexible joints for those that do. A...
Read more >
Structural Tree Support Systems
Structural support systems, also known as cabling or bracing, can help prolong the shape and structure of a comprimised tree. Support cabling is...
Read more >
CURA - Tree Supports vs Standard Supports - YouTube
Chuck shows you how to setup Cura Tree Supports vs Simple Supports and all the settings in this episode. He uses a simple...
Read more >
Generation of a tree-like support structure for fused ...
First, an initial tree-like support structure is generated according to the ... Then, automatic generation software is developed for support structures, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found