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

Prisma object with self-referencing object #1358

Open
julioxavierr opened this issue Dec 12, 2024 · 13 comments
Open

Prisma object with self-referencing object #1358

julioxavierr opened this issue Dec 12, 2024 · 13 comments

Comments

@julioxavierr
Copy link
Contributor

julioxavierr commented Dec 12, 2024

I am using the Prisma Plugin and I would like to group some information from my model when defining my Prisma Object. I'll use an example to contextualize, but also help to follow up and open a PR if this is interesting.

I have a model that is defined as following:

model User {
  id                    String     
  name                  String
  streetAddress         String
  city                  String
  state                 String
  country               String
  postalCode            String    
}

However, in order to keep the address information organized, I'd like to expose it in the GraphQL API as such:

query {
  user {
    id
    name
    address {
      streetAddress
      city
      state
      country
      postalCode
    }
  }
}

Currently, I'm achieving that with a "simple object" for my address, but then, the user 'address' field selects all the fields from the database:

const Address = builder.simpleObject("Address", {
  fields: (t) => ({
    streetAddress: t.string(),
    addressLocality: t.string(),
    addressRegion: t.string(),
    addressCountry: t.string(),
    postalCode: t.string(),
    fullAddress: t.string(),
  }),
})

builder.prismaObject("Property", {
  fields: (t) => ({
    address: t.field({
      type: Address,
      select: { address: true, city: true, state: true, country: true, postalCode: true },
      resolve: async (source) => source,
    }),
  }),
});

However, oftentimes I only need to show the 'city', which ends up fetching all the other address fields. I'd like to understand if there is another way to do this that would prevent overfetching data.

@hayes
Copy link
Owner

hayes commented Dec 16, 2024

You can use the variant api for this https://pothos-graphql.dev/docs/plugins/drizzle#variants

This will allow you to define multiple types based on the same model, and then use t.variant to define fields that reference the same instance of that model

@hayes hayes closed this as completed Dec 16, 2024
@hayes
Copy link
Owner

hayes commented Dec 16, 2024

sorry, those are the docs for the drizzle equivalent. Trying to track down why this is missing from the prisma docs. It used to be there, but I can't find it now

@hayes hayes reopened this Dec 16, 2024
@hayes
Copy link
Owner

hayes commented Dec 16, 2024

Here's the correct doc: https://pothos-graphql.dev/docs/plugins/prisma/variants

I'd recommend skimming through the whole prisma docs again. There was a lot missing, and there might be some other relevant docs that have been restored now

@hayes hayes closed this as completed Dec 16, 2024
@julioxavierr
Copy link
Contributor Author

julioxavierr commented Dec 18, 2024

@hayes thanks! I was able to implement it as described in the new docs.

It seems to work as expected for simple queries, but it crashed when I tried using it with more complex queries – that had fragments, etc. It looks like it didn't include some of the fields in the prisma query. I'll try to reproduce it in a demo and create a separate issue in the future.

@hayes
Copy link
Owner

hayes commented Dec 18, 2024

Interesting. If you can find a way to reproduce it, that would be great!

Can you double check that you are spreading the query argument into all your Prisma queries when using t.prismaField? That's one of the only ways I've seen this plugin error in the past

@julioxavierr
Copy link
Contributor Author

julioxavierr commented Dec 18, 2024

This is not a demo yet, but just in case it's useful, here's a simplified version of how it's being implemented:

const User = builder.prismaNode("User", {
  id: { field: "id" },
  select: { id: true },
  fields: (t) => ({
    id: t.exposeID("id"),
    name: t.exposeString("name", { nullable: false }),
  }),
});

const UserAddress = builder.prismaObject("User", {
  variant: "UserAddress",
  fields: (t) => ({
    streetAddress: t.exposeString("address"),
    city: t.exposeString("city"),
    state: t.exposeString("state"),
  }),
});

builder.prismaObjectField("User", "address", (t) => t.variant(UserAddress));

builder.queryFields((t) => ({
  user: t.prismaField({
    type: User,
    args: {
      id: t.arg.id({ required: true }),
    },
    resolve: async (query, _source, args) => {
      return prisma.user.findFirst({ ...query, where: { id: args.id } });
    },
  }),
}));

I noticed that if I query only for the user name, the "query" variable in the user resolver has the expected fields:

query {
  user(id: "...") {
    name
  }
}
>>> { query: { select: { id: true, name: true } } }

However, if I query the address fields, the "query" variable is an empty object:

query {
  user(id: "...") {
    name
    address {
      streetAddress
    }
  }
}
>>> { query: { } }
Cannot return null for non-nullable field User.name.

@hayes
Copy link
Owner

hayes commented Dec 18, 2024

This seems indicative of User having a default select (not shown in your example) and Address not having a default selection, effectively resulting in a select *

The selection for User would be id and name, but when its merged with the selection for Address, you select all columns. Not providing a select is how Prisma queries for all columns implicitly.

The query you logged SHOULD return all columns, because it doesn't have a select block. Id be curious what the Prisma query returns, in this case.

@hayes hayes reopened this Dec 18, 2024
@julioxavierr
Copy link
Contributor Author

This seems indicative of User having a default select (not shown in your example) and Address not having a default selection, effectively resulting in a select *

That's correct. I've updated my example to include that, and also changed it to use "prismaNode".

The query you logged SHOULD return all columns, because it doesn't have a select block. Id be curious what the Prisma query returns, in this case.

As you've mentioned, the prisma query seems to use the default selection, even though the the 'query' from the prismaField is not provided.

Query: SELECT "t1"."id" FROM "public"."users" AS "t1" WHERE "t1"."id" = $1 LIMIT $2
Params: ["6d30dfa7-f7d3-468e-990a-9e3ab73b320d",1]
>> { result: { id: '6d30dfa7-f7d3-468e-990a-9e3ab73b320d' } }

I've tried the following, but the same behavior still seems to happen:

  1. Remove the default selection from the prismaNode
  2. Added the default selection to the variant, so it matches the prismaNode's default selection

@julioxavierr
Copy link
Contributor Author

Also, not sure if that could help, but I also noticed that the issue does not happen if my node is nested in a connection:

query {
  product(id: "69652d36-3126-90a9-9c9a-dac35103f3e5") {
    users {
      edges {
        node {
          name
          address {
            streetAddress
          }
        }
      }
    }
  }
}

@hayes
Copy link
Owner

hayes commented Dec 19, 2024

Can you log out the actual the query and result in the resolver. Based on what you are saying it sounds like prisma isn't doing the right thing.

I suspect either there is something odd about your Prisma setup, or our understanding of the problem is off (eg, the original query is correct, but Pothos triggers a second query with a different selection)

@hayes
Copy link
Owner

hayes commented Dec 19, 2024

Oh, sorry it. Looks like you did log out the result...

If that is the only query, and the query object really doesn't contain a select (as described in your earlier response) then Prisma isn't working as intended.

Can you confirm that the query object still is an empty object? Sounds like you've changed a lot since then

@julioxavierr
Copy link
Contributor Author

I suspect either there is something odd about your Prisma setup

Makes sense, I'll investigate further and try to set up a proper demo when I have some time. Thanks for the support

Can you confirm that the query object still is an empty object?

I confirm that the query object is still an empty object.

Sounds like you've changed a lot since then

Honestly, I didn't change much; the setup is just as I described here. The other things I mentioned were simply experiments to see if they would produce any different results.

@hayes
Copy link
Owner

hayes commented Dec 19, 2024

If I am understanding this correctly, then this is an issue on the prisma side.

To be very clear:

prisma.user.findFirst({ ...{ /* empty object */ }, where: { id: args.id } })

is returning an object with only the id. This isn't how prisma is supposed to work, so either there is a bug in prisma, or one of the previous assumptions is wrong

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants