We want to get back this result
{
project: {
id: 1,
name: "First Project",
description: "Create a cool website",
client: {
id: 1,
name: "First Client"
},
tasks: [
{
id: 1,
name: "Task One"
},
{
id: 2,
name: "Task Two"
}
]
}
}
The GraphQL query looks like this, and in the comment we have the sql being generated/defined by the resolver for each field
query ProjectOne {
project: projects (id: 1) { -- SELECT * FROM projects WHERE id = 1
id, -- SELECT id FROM projects
name, -- SELECT name FROM projects
description, -- SELECT description FROM projects
client: clients { -- SELECT * FROM clients
id, -- SELECT id FROM clients
name -- SELECT name FROM clients
},
tasks (completed: false): { -- SELECT * FROM tasks WHERE completed = false
id, -- SELECT id FROM tasks
name -- SELECT name FROM tasks
}
}
}
When the SQL execution engine walks this GraphQL AST, the result of executing each node is a value defined by an ADT for representing a limited part of SQL, so at this point, resolvers are pure functions. After the first step (execution of each resolver), the result basically is a "Tree Query" where Query is something like: data Query = Select {fields::[String], from::[String], where_::[Condition], ...} | .... The second part that is to fold this tree based on the fact that the Sql ADT is part of Monoid typeclass and by that i mean that the follwing queries
SELECT * FROM projects WHERE id = 1
SELECT id FROM projects
SELECT name FROM projects
SELECT description FROM projects
collapse into
SELECT id, name, description FROM projects WHERE id = 1
The same is true for clients/tasks So after the second step (actually 2.1) We have a tree like this
SELECT id, name, description FROM projects WHERE id = 1
SELECT id, name FROM clients
SELECT id, name FROM tasks WHERE completed = 1
Based on relations between projects/clients/tasks (not sure how that is specified by the schema or resolver know that beforehand, but the db can used for introspection to determine the foreign keys for relations), this tree of queries can be folded/collapsed into an end result query
SELECT array_to_json(array_agg(row_to_json(t)))::CHARACTER VARYING AS json
FROM (
WITH clients AS (
SELECT clients.id, clients.name FROM clients
)
SELECT
projects.id,
projects.name,
projects.decription,
row_to_json(clients.*) AS client,
(
SELECT array_to_json(array_agg(row_to_json(tasks)))
FROM (
SELECT tasks.id, tasks.name
FROM tasks
WHERE tasks.project_id = projects.id
) tasks
) AS tasks
FROM projects, clients
WHERE projects.client_id = clients.id
) t;
This idea above seems limited to the fact that The SQL backend is not allowing general SQL being used in resolvers and also, this backend is tied to a single database for which we know all the relations and i am not sure if FB intended each resolver to be independent of each other (i.e. one resolver to be SQL and another be a pure function )
So my understanding so far of how the "execution" part is working in graphql-js is that each resolver returns basically a scalar/array/object which obviously have a json representation. So the "interpreter" walks the ast and executes each resolver and collects these objects in a tree and at the end it does a json encode.
What this (probably) means in Haskell is that each resolver needs to return a value or type "a" with the constraint that "a" has a "ToJSON" instance from Aeson. So since these values can come from 2 places (pure and IO) then the return type of the resolver could be something like "(ToJSON a) => IO a"