Graphcool / graphcool-framework

Apache License 2.0
1.77k stars 130 forks source link

Field resolver functions #15

Open marktani opened 6 years ago

marktani commented 6 years ago

Issue by schickling Thursday Dec 22, 2016 at 14:38 GMT Originally opened as https://github.com/graphcool/prisma/issues/56


Computed fields allow you to extend a model's schema by executing functions to calculate the value of the field for every request.

marktani commented 6 years ago

Comment by marktani Tuesday Jan 10, 2017 at 14:06 GMT


Another use case are computed nodes.

marktani commented 6 years ago

Comment by zth Thursday Jan 12, 2017 at 19:23 GMT


I'm building an app where you'll regularly answer forms. My use case will be calculating averages and scores for those forms on the fly for certain periods of time, without needing to fetch all the data on the client and calculate it there.

marktani commented 6 years ago

Comment by marktani Monday Feb 06, 2017 at 22:47 GMT


compute the millisecond representation of a DateTime field

This is useful for the Algolia integration as Algolia expects that format for times. But better handled by #91.

marktani commented 6 years ago

Comment by wallslide Monday Mar 06, 2017 at 13:57 GMT


I'd also find this functionality extremely useful. I can see needing to have parts of dates for different queries. The month, the day, day of week. It would be helpful if I could just add a new computed property when I need it rather than having to update my schema and go back and calculate the value for all nodes.

marktani commented 6 years ago

Comment by tonyxiao Thursday Mar 09, 2017 at 18:02 GMT


This would be HUGE. Without it I find it to be hugely limiting to be using a hosted GraphQL service rather than just write my own GraphQL server. Effectively this is like being able to create my own resolver function right?

Here's my use case while trying to create a waitlist app. I want to create the following type

type Reservation {
  id: String!
  email: String!
  peopleAhead: Int!
  peopleBehind: Int!
  referralCode: String!
}

However peopleAhead and peopleBehind depends on how many ppl are there on the waitlist in total, which is not a stored field but rather computed field

marktani commented 6 years ago

Comment by mattfysh Saturday Jul 29, 2017 at 13:50 GMT


Super useful. Also wondering whether you'd be applying a caching layer to the computed result? One that's invalidated in certain circumstances, e.g:

1 - function to compute value changes 2 - underlying source data changes

At this stage, a workaround might be to store the result statically against the object, and updating via server-side subscription. Will also need a dummy value that can be updated on the object which triggers a refresh of the computed value (and an external tool which batches up this process for all objects)

marktani commented 6 years ago

Comment by kbrandwijk Saturday Jul 29, 2017 at 13:57 GMT


A lot of these use cases could actually be implemented using RP hooks, if you can calculate the values at the moment you're saving/updating a node. The other usecases are clearly about querying, where the values cannot be calculated in advance. For these specific use cases, calculated fields would be a great option.

marktani commented 6 years ago

Comment by mattfysh Sunday Jul 30, 2017 at 03:44 GMT


The use case I'm looking for is updating an average score e.g. on a Product based on all of its Reviews

marktani commented 6 years ago

Comment by tristancaron Wednesday Nov 22, 2017 at 12:44 GMT


Is there a workaround at the moment?

marktani commented 6 years ago

Comment by vincenzo Thursday Nov 23, 2017 at 10:46 GMT


@tristancaron Was wondering that myself, but I suspect the only workaround at the moment is to implement your customer query using a resolver function. This, effectively, would be the logic you'd have implemented for the computed field. However, in this case, you'll have to make two requests, rather than a single one (which kind of defeats one of the main "pros" of GraphQL).

@marktani @kbrandwijk Any other way to work around this using a single request?

ahsanwtc commented 6 years ago

The thing I am trying to do is have a resolver function which wraps our external places API and I can use that resolver as a field in my other models. Don't know if it fits the profile here.

dweldon commented 6 years ago

While you can write your own field resolver functions, the challenge is the necessary data for a given resolver may not be provided as part of the query itself.

Using the canonical User and Post example, say you wanted a resolver for postCount on User. The resolver for postCount would need to know the user's id to perform the aggregation. You could imagine a query like:

{
  users {
    id
    postCount
  }
}

In this case the resolver for postCount would know the user's id and could provide the answer. However, if the query looked like:

{
  users {
    username
    postCount
  }
}

The resolver wouldn't have the necessary id to do the lookup. It's nice that prisma loads the minimal set of data from the db at the time of the query, but in this case it severely limits the flexibility of the framework.

It seems like one way to guarantee the ids are always loaded, is to drop the info parameter from the users resolver, which would then load the entire record from the db. For example:

users(parent, args, ctx) {
  return ctx.db.query.users();
}

instead of:

users(parent, args, ctx, info) {
  return ctx.db.query.users({}, info);
}
marcovc commented 6 years ago

This is what I did (I'm using the mysql backend):

  1. created the computed field as a standard field in "datamodel.graphql"
  2. using mysql client in the db client, deleted the column and recreated it as a "generated" column (essentially a computed field)

Seems to be working...

julius-e commented 5 years ago

I'm trying to implement a function that applies custom linear weights to a set of stats and return the total computed value for each row.

Typically one would send the weights to the server in the request, i.e.:

# Linear weight to apply to each stat. Any field on Player can have a weight.
type Weights {
  stat1: Int,
  stat2: Int,
  ...
  statN: Int
}

# A 'Player' has stats.
type Player {
  stat1: Int,
  stat2: Int,
  ...
  statN: Int,

  #This field is the goal of the exercise:
  #Its value is given by: `SUM((stat1 * weight1),  ... (statN * weightN))`
  scrore: Int
}

type Query {
  getScores(weights: Weights, skip: Int, take: Int, orderBy: score_Desc): [Player!]!
}

This would be trivial in SQL:


SELECT stat1, stat1 * :weight1, stat2, stat2 * :weight2, ... statN, statN * :weightN
FROM PLAYERS
ORDER BY stat1 * :weight1 + stat2 * :weight2 + ... statN * :weightN DESC
LIMIT 10

I can eschew Prisma for this task and add a custom query + resolver in my application layer graphql server, but it would no longer conform to the API exposed by Prisma. Things like pagination and cursors would have to either be manually implemented or refactored into another paging mechanism. (i.e. the skip + take params above).

Ideally a lambda that gets evaluated for each record at data retrieval time would be ideal. Obviously I do not know what is needed to make that happen, but I figured explaining my use case would be useful to the discussion since it seems pretty representative of the issue.

BTW I love Prisma. Keep up the great work!