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

Improved relational client API: $nested queries #3668

Open
schickling opened this Issue Dec 6, 2018 · 7 comments

Comments

Projects
None yet
8 participants
@schickling
Copy link
Member

schickling commented Dec 6, 2018

Is your feature request related to a problem? Please describe.

As described in e.g. #3104, the current Prisma client API is quite limited when it comes to more complex relational queries (i.e. you can just query exactly one level of nodes). As a fallback it's possible to use the $fragment API which however can feel a bit cumbersome and lacks better tooling to make it nice to use (e.g. auto-completion for the GraphQL query/fragment + auto-generated interfaces for the response type generics)

Describe the solution you'd like

I'm proposing to add the following API to allow for more complex, type-safe, relational queries using the given programming language instead of having to fallback to GraphQL. This allows you to additionally to the user also deeply fetch the user's friends and posts including their comments.

const dynamicResult: DynamicResult = await prisma
  .user('bobs-id')
  .$nested({ posts: { comments: true }, friends: true })

// assuming the following type definitions
type Post = {
  id: string
  title: string
  body: string
}

type Comment = {
  id: string
  text: string
}

type User = {
  id: string
  name: string
}

// Note: This is just here for the sake of demonstration and would be automatically
// derived based on conditional types in TS
type DynamicResult = {
  posts: (Post & { comments: Comment[] })[]
  friends: User[]
}

The idea is to mimic a GraphQL query in JS (or languages):

  • To express whether you fetch a field/relation you set it to true or false. Empty/nested objects are equivalent to specifying true.
  • Scalars true by default Similar to how the client automatically fetches all scalar fields, the queried relations inside the $nested query also contain all scalar values by default.
  • Relations false by default Also same as for the typically client behavior, relations are not fetched by default but can be fetched by setting the corresponding field to true or { ... }.
  • The response value is strongly typed given on which fields you're querying. In TS this can be derived automatically using conditional types. In other programming languages this needs to be handled via generics (+ optionally code generation).

The $nested API works both on the root level (e.g. prisma.$nested({ ... })) as well on a node level (e.g. prisma.users().$nested({ ... }). See full example:

const nestedResult = await prisma.$nested({
  users: {
    $args: { first: 100 },
    firstName: false,
    posts: {
      comments: true,
    },
    friends: true,
  },
})
@SpaceK33z

This comment has been minimized.

Copy link
Contributor

SpaceK33z commented Dec 7, 2018

One thing I'm still missing in this proposal compared to how prisma-binding works now is how to use the info parameter from the GraphQL resolver to automatically fetch only the relations that are used in the query.

query {
  events {
    id
    venue {
      id
    }
  }
}

The above would be a query I sent to my Apollo Server. In the resolver, I then would use prisma.events(). In this case, I also want it to fetch the venue relation.

However, if venue { id } would not be a part of the query I don't want it to fetch that relation.

With your proposal I don't see how that situation would be fixed, but I could be wrong of course 😄. Or maybe this would be out of scope for the prisma-client and there should be a separate package that translates the info param to the $nested syntax in your proposal?

@schickling

This comment has been minimized.

Copy link
Member Author

schickling commented Dec 7, 2018

Thanks for bringing this up. Prisma client is GraphQL agnostic by design. However, we're currently working on some really exciting features/improvements that let you build GraphQL servers with a similar efficiency and minimal boilerplate while keeping all the benefits of Prisma clients.

More on this soon. Please let's keep this issue focussed on the proposed API design and related topics.

@sibelius

This comment has been minimized.

Copy link

sibelius commented Jan 3, 2019

this is basically populate of mongoose, but much more powerful

the problem is how to make it fast

when we moved to GraphQL we just avoid populate as it makes harder to make it type safe

@capaj

This comment has been minimized.

Copy link

capaj commented Jan 3, 2019

@schickling looking at objection I am a bit surprised they've got the same-I've never visited this corner or the docs, even when working daily with it: http://vincit.github.io/objection.js/#relationexpression-object-notation

so you're on the same track. Love that.
Just one question on dynamic relation nesting-are you sure you won't need:

{
  parent: {
    $recursive: 5
  }
}
// equals
{
  parent: {
    parent: {
    parent: {
    parent: {
     parent: true
  }
  }
  }
  }
}

or maybe

{
  parent: {
    $recursive: 'all'
  }
}

?

@jasonkuhrt

This comment has been minimized.

Copy link
Contributor

jasonkuhrt commented Jan 5, 2019

@capaj maybe a better name would be $depth?

The idea is to mimic a GraphQL query in JS (or languages):

@schickling Like that premise.

@steida

This comment has been minimized.

Copy link

steida commented Jan 11, 2019

Somewhere in docs should be noted that related queries can be resolved with this:

https://www.prisma.io/forum/t/can-not-return-subfields-of-custom-types-in-query-with-prisma-client/4427/3?u=daniel_steigerwald

I was using string fragment workaround until I realized, that TypeScript interfaces for graphqlgen should be flat and missing non-scalar fields like creator (User) etc. should be resolved elsewhere.

import { WebResolvers } from '../generated/graphqlgen';

export const Web: WebResolvers.Type = {
  ...WebResolvers.defaultResolvers,

  creator: (parent, _, ctx) => {
    return ctx.db.web({ id: parent.id }).creator();
  },
};

So $nested use case should be better explained IMHO.

Update: Maybe it's wrong, I don't know.

@FrankSandqvist

This comment has been minimized.

Copy link

FrankSandqvist commented Jan 17, 2019

I would also love a tool that would be able to translate Apollo's GraphQLResolveInfo object "info" into these nested queries.

I see that the $fragment function now takes a DocumentNode object in addition to a string, but I have no idea how to use DocumentNode in this case.

The previous prisma-binding worked with the info object out of the box, and that was great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.