-
-
Notifications
You must be signed in to change notification settings - Fork 432
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
Client side querying #312
Comments
Thanks for starting this, great idea! This is how i would change your suggested schema: input QueryConstraints {
where: [WhereConstraint!]
orderBy: [OrderByConstraint!]
}
# We need this as the value for a WhereConstraint can basically be anything
scalar Mixed
input WhereConstraint {
column: String!
operator: Operator
value: Mixed!
boolean: LogicalOperator
}
enum Operator {
IN @enum(value: "IN")
NOT_IN @enum(value: "NOT IN")
EQUALS @enum(value: "=")
NOT_EQUALS @enum(value: "!=")
LIKE @enum(value: "LIKE")
NOT_LIKE @enum(value: "NOT LIKE")
GREATER_THAN @enum(value: ">")
GREATER_THAN_OR_EQUAL @enum(value: ">=")
LESS_THAN @enum(value: "<")
LESS_THAN_OR_EQUAL @enum(value: "<=")
}
input OrderByConstraint {
column: String!
order: Order!
}
enum Order {
ASC @enum(value: "ASC")
DESC @enum(value: "DESC")
}
enum LogicalOperator {
AND @enum(value: "AND")
OR @enum(value: "OR")
} For more complex queries, it is not practical (or safe) to allow the user to control it. So in that case, the user may just build the query themselves and use a custom resolver. |
Thanks @spawnia ! Here is what I thought… Think of rather complex example SELECT * FROM table WHERE (A = B OR C >= D) AND (E LIKE F AND G IN H) How would you like to represent this in the query? For me, this is much better. {
"query": {
"relation": "AND",
"conditions": [
{
"relation": "OR",
"conditions": [
["A", "B"],
["C", ">=", "D"]
]
},
{
"relation": "AND",
"conditions": [
["E", "LIKE","F"],
["G", "IN", "H"]
]
}
]
}
} Laravel do check the if the operator is valid or not, so this shouldn't be a security issue I think, or maybe we can just wrap the query in a try catch block if the operator is invalid throw an error message to the API consumer.
That's well said. Well… the best solution maybe is just this… And parse it into an AST or object something like above. (A = B OR C >= D) AND (E LIKE F AND G IN H) I'd like to know what's your opinion for this. |
We have to strike a balance between simplicity and functionality. I do not think that short syntax is necessarily simple, it does not hurt to write out a few {
foo(query: {
where: [
{column: "bar", operator: "EQUALS", value: "baz"}
]
}){
bar
baz
} |
For the combining of logical operator, we might do something similar to Prisma, like https://www.prisma.io/docs/prisma-graphql-api/queries-qwe1/#combining-multiple-filters input QueryConstraints {
where: [WhereConstraint!]
orderBy: [OrderByConstraint!]
}
input WhereConstraint {
column: String!
operator: Operator
value: Mixed!
AND: [WhereConstraint!]
OR: [WhereConstraint!]
NOT: [WhereConstraint!]
} |
@spawnia That's looks so great for me! maybe |
@spawnia type Query {
# This is the fully automated version.
# Using the `@queryable` to generate the argument definition.
# and `@queryable` doesn't have to be used along with `@paginator`.
# in that case we should give the model name to it `@queryable(model: "User")`
users(query: QueryConstraints): [User!]! @paginate(type: "paginator" model: "User") @queryable
# or optionally if the user really want their own query logic, they can do it by themselves
users(query: QueryConstraintsDefinedByUser): [User!]! @paginate(type: "paginator" querybuilder: "className@build")
} |
I think type Query {
users: [User!]! @paginate(queryable: true)
} This will suffice, as the argument can be generated and the other values are defaults. Custom query building only really makes sense for the type Query {
users(userDefined: ArgsOfAnyType): [User!]! @paginate(builder: "App\\User@customUserQuery")
} |
@spawnia type Query {
users: [User!]! @queryable
} But… While I was reading the Prisma's doc, I found they really provide their users a simple and intuitive way to CRUD the data model. So in their implementation, if I want all of the filtered results, what I have to do Is just as simple as omitting the type Query {
users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
} maybe we should rethink this topic from zero, spread the topic to something like this type User @crud {
...
} this way, in most cases, all my works are just as simple as adding a |
That would be
I brought this up a while ago. I think this is waaaaay more complex then we both imagine, and should really be it's own package. Lighthouse is not built for this. Let's find a nice middleground that is both flexible and convenient. |
Cool!!
Agree with this! Lighthouse itself should be focus on the most low level core components. |
@spawnia I came up with an idea is that we could map the model back to the GraphQL schema, that is, model driven schema generation. Manage the object fields in one place(model class), so you never have to sync the schema file again and again, which I think is a better solution for Laravel developers. I'm developing a package of Lighthouse for just that purpose now. |
This idea sounds awesome. I have also been looking at Prisma and it has a lot of awesome features. Especially the operators are really useful and it was something I missed when I first tried Ligthouse. @yaquawa We've already developed a package for CRUD generation with Lighthouse, you can check it out here: https://github.com/deInternetJongens/Lighthouse-Utils |
Big schemas are pretty problematic, but can be managed. We have over 10000 LOC in ours, it kept crashing on the initial parse until we enabled PHP Opcache. I did consider splitting the parsed AST by type and caching them seperately and only deserializing them lazily. This would require quite the internal overhaul, but should be manageable. |
Thanks @maarten00 ! While I think auto generation of schema probably is the best way to make the crud API for the data model. What I'm creating now is define the fields in model class but not in the schema file it self, so we can manage the crud API in a single source of truth approach. |
@spawnia Good idea! just like how the the PHP autoloader works. |
As we are just discussing this in Slack, here is how an example query could look like. This gets actors over age 37 who either have red hair or are over 150cm. {
people(
filter: {
where: [
{
AND: [
{ column: "age", operator: ">", value: 37 }
{ column: "type", value: "Actor" }
{
OR: [
{ column: "haircolour", value: "red" }
{ column: "height", operator: ">", value: 150 }
]
}
]
}
]
}
){
name
}
} And results in the following SQL: SELECT name
FROM people
WHERE age > 37
AND TYPE = actor
AND (haircolour = red
OR height > 150); Ninja Edit: Correct SQL. Thanks @mfn |
I believe the SQL operator is wrong Non-Ninja Edit: already fixed! :) |
@spawnia I was just looking back at your suggestion, but I see it is also making use of input types in your Our API is experiencing quite some perfomance problems because of the huge amount of parameters on our queries. I'm looking for a way to fix it myself (in our Lighthouse Utils package) without waiting for this feature.. So I wanted to have a go at it myself, but I'm wondering if it would even be possible without input type support? |
Input types in general are not an issue. The particular problem i think you are referring to has to do with directives that influence a query, while being defined on the arguments of that query. For example I think there are multiple problems with the existing filter directive. They do not scale well. Every query option has to be defined in advance, which either grows the amount of arguments to a ridiculous amount, or leaves the user with very limited query capabilities. The client has no idea what filtering is going on behind the scenes. Take the following definition as an example: type Query {
foo(name: String @neq): Bar
} When introspecting on the schema, the client actually only sees that there is an argument They are complicated to implement. We have to do some weird tricks to reach into the field resolution process from the argument definition. This mechanism only gets worse once you add InputObjects into the mix. It would certainly be possible, but actually i think we should not do it. |
In a discussion with @robjbrain we developed the need for multiple shapes for the query InputObject. I think that Lighthouse might easily have a mechanism for plugging in such implementations. Queryable directives, such as |
Any idea when client-side querying will be implemented into Lighthouse? |
Since we have |
(Related to #302)
Currently, lighthouse only allows users to define query constraint at server side with those like
@eq
kind of directives.By doing this way, we are forced to define as many fields as our query constraint types.
We have to hard code every type of query constraint when there comes a need. (which is kind of violating the Open/closed principle)
My suggestion is
then at the client side, you can define your query like this
Also the same ability can be shared between the
@hasMany
@belongTo
@all
@first
directives too.The initial idea can be found at #302, because this is a rather larger topic, I opened this issue.
I want to know what do you think, thanks!
The text was updated successfully, but these errors were encountered: