Skip to content

query based on fields of complex variables #248

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

Open
sorenbs opened this issue Dec 2, 2016 · 12 comments
Open

query based on fields of complex variables #248

sorenbs opened this issue Dec 2, 2016 · 12 comments
Labels
👻 Needs Champion RFC Needs a champion to progress (See CONTRIBUTING.md) 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)

Comments

@sorenbs
Copy link

sorenbs commented Dec 2, 2016

Complex variables are described in the spec https://facebook.github.io/graphql/#sec-Variables-Are-Input-Types

This example takes a ComplexInput and passes it to the findDog query:


query takesComplexInput($complexInput: ComplexInput) {
  findDog(complex: $complexInput) {
    name
  }
}

What I would really like to do is use specific fields in the ComplexInput variable like this:

  findDogByName(name: $complexInput.name) {
    name
  }
}

This is not valid GraphQL, but it would simplify a feature we are working on quite a bit.

For context: Graphcool is a backend application platform that combines GraphQL and Lambda. Many of our features are based on automatically generated schemas and stored queries we execute on the server in response to some event, so our needs might differ significantly from the broader community.

@benwilson512
Copy link

I guess I'm trying to figure out if you're only using the name why you wouldn't just pass in the name instead of the whole complex input?

@sorenbs
Copy link
Author

sorenbs commented Dec 4, 2016

@benwilson512
Our use case is quite unusual.

The query is an extension point of our backend service. The developer should be able to use any number of variables in the query but isn't required to use all of them. One way to handle this is to have a separate ui where the developer picks exactly the variables they want included and we include these as individual variables. There are two reasons I won't to avoid this approach. 1) it introduces extra ui. 2) we would have to prefix the variables as they are now top level and could conflict with other variables we would add in the future.

Is this use case clear or should I try to make it more concrete?

The cleanest solution I could come up with for our use case is to allow accessing fields of variable objects in the query. My proposed syntax aligns with the proposal in #174 to enable flattening of object arrays

@benwilson512
Copy link

Isn't that just solved by doing:

query takesComplexInput($complexInput: ComplexInput) {
  findDog(complex: $complexInput) {
    name
  }
}

And having the resolvers handle the fact that their inputs are nested 1 layer deep?

I guess my overall feeling here is that this is new syntax, new validation rules, and several other new capabilities to solve a problem that would be solved by either having a more nuanced client or by just nesting.

@sorenbs
Copy link
Author

sorenbs commented Dec 4, 2016

The issue with that solution is that the findDog resolver is generic and can filter by any number of fields. in your example it would filter by all fields on the complexInput, but really the task we ask the developer to do here is specify what field to filter the dogs by.

The Dog example is simple and doesn't make too much sense, but should illustrate what we are trying to do.

@calebmer
Copy link

calebmer commented Dec 6, 2016

In this case ComplexInput would need to be defined in the schema, correct? If GraphQL were to support complex input types should probably also be definable in the query.

Either inline like so:

query takesComplexInput($complexInput: { name: String! }) {
  findDogByName(name: $complexInput.name) {
    name
  }
}

or with a local type definition:

input ComplexInput {
  name: String!
}

query takesComplexInput($complexInput: ComplexInput) {
  findDogByName(name: $complexInput.name) {
    name
  }
}

@sorenbs
Copy link
Author

sorenbs commented Dec 28, 2016

It certainly has to be defined in the schema. I'm not sure why that is different from the current situation - I'm probably missing something. Could you elaborate?

@jameswyse
Copy link

jameswyse commented Feb 6, 2017

This would also be super useful for variables used in directives like @skip and @include. I recently started using these directives and ended up having to pass a lot of variables separately; which is becoming hard to manage and prone to errors.

For example I'd much prefer this:

input SkipInput {
  foo: Boolean,
  bar: Boolean,
  baz: Boolean
}

query MyQuery($skip: SkipInput!) {
  fooField @skip(if: $skip.foo)
  barField @skip(if: $skip.bar)
  bazField @skip(if: $skip.baz)
}

Over this:

query MyQuery($skipFoo: Boolean!, $skipBar: Boolean!, $skipBaz: Boolean!) {
  fooField @skip(if: $skipFoo)
  barField @skip(if: $skipBar)
  bazField @skip(if: $skipBaz)
}

@stubailo
Copy link
Contributor

I feel like this would be even cooler with #251 (remove variable definitions) so you could do:

query MyQuery {
  fooField @skip(if: $skip.foo)
  barField @skip(if: $skip.bar)
  bazField @skip(if: $skip.baz)
}
{
  skip: { foo: true, bar: false, baz: true }
}

Then you don't even need the input definition.

@mbleigh
Copy link

mbleigh commented Jun 29, 2023

Big 👍 to this issue. In our case we we have generic resolvers and want to be able to declaratively map application-specific input types (e.g. values of a form) to a generic GraphQL schema using only the operation definition:

input ProductListFilter {
  partialName: String
  minStarRating: Int
  maxPrice: Int
}

query ListProducts($filters: ProductListFilter) {
  products(where: {
    avgRating: {_gte: $filters.minStarRating},
    name: {_like: $filters.partialName},
    price: {_lte: $filters.maxPrice}
  }) { id, name, price, avgRating }
}

The critical idea here is that we want the input to match the shape that makes sense to the client application while translating that to the generic querying system of the resolver.

@benjie
Copy link
Member

benjie commented Jun 29, 2023

@mbleigh Your example doesn't seem particularly compelling, since you could instead issue it using existing syntax with a slight tweak to the variables you supply:

query ListProducts(
  $partialName: String
  $minStarRating: Int
  $maxPrice: Int
) {
  products(where: {
    avgRating: {_gte: $minStarRating},
    name: {_like: $partialName},
    price: {_lte: $maxPrice}
  }) { id, name, price, avgRating }
}

Could you expand on why this isn't a suitable solution? When doing so, please keep the Guiding Principles in mind.

@mbleigh
Copy link

mbleigh commented Jun 29, 2023

I think the two core issues problems with the existing syntax that I can't address easily are:

  1. Namespacing
  2. Reusability

In a simple example like the above I'd agree that variables are a reasonable replacement. But in a much more complex UI, I might have 5-6 similarly complex filters / fetches happening at once. Nesting provides much more comprehensible grouping and namespacing for related inputs than, say, using prefixes (e.g. 3 vars with a product_ prefix, 3 vars with a conversation_ prefix, 3 vars with an order_ prefix, and so on) becomes a pretty unmaintainable mess and if I have multiple pages with different mixes of the same inputs, I now have to duplicate a lot.

Let's take a look at another example focused around reusability. We have a system that will automatically generate and expose CRUD operations based on type definitions. So a Product type will have productCreate, productUpdate, and productDelete mutations automatically generated.

For application logic, the fields are always going to be the same and can reasonably be mapped to a ProductInput containing all user-settable fields. So it makes sense to have something along the lines of productCreate(data: ProductInput!) and productUpdate(id: ID!, data: ProductInput!) and productBatchCreate(data: [ProductInput!]!) etc.

When I'm consuming these generated mutations (which I don't have direct ability to modify), I want to be able to reuse these same data types but reuse only parts of what's provided based on the specific needs of the operation for the app, and override parts of the input with my own "constants":

type Product {
  id: ID!
  name: String!
  description: String
  price: Int
  inventory: Int
  status: ProductStatus!
  createTime: Timestamp!
  updateTime: Timestamp!
}

# generated automatically
input ProductInput {
  name: String
  description: String
  price: Int
  inventory: Int
  status: ProductStatus
  createTime: Timestamp
  updateTime: Timestamp
}

mutation createProduct($data: ProductInput) {
  createProduct(data: {
    name: $data.name,
    description: $data.description,
    price: $data.price,
    inventory: $data.inventory,
    status: PENDING_REVIEW # OrderStatus is fixed and not derived from input
    createTime: {now: true} # Timestamp is a scalar with special parsing for this sentinel value
    updateTime: {now: true} # Timestamp is a scalar with special parsing for this sentinel value
  })
} { ... }

mutation updateProduct($id: ID!, $data: ProductInput) {
  updateProduct(id: $id, data: {
    name: $data.name,
    description: $data.description,
    price: $data.price,
    inventory: $data.inventory,
    updateTime: {now: true}
  })
} { ... }

Without being able to do property accessors for the input type, I couldn't use the auto-generated input type as a variable and would have to repeatedly define the same set of variables over and over again.

@benjie
Copy link
Member

benjie commented Jun 29, 2023

Thank you for sharing 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
👻 Needs Champion RFC Needs a champion to progress (See CONTRIBUTING.md) 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)
Projects
None yet
Development

No branches or pull requests

8 participants