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 )
At the same time if resolvers are only of type IO a, then when dealing with sql, each one would have to actually execute a query which does not allow "building" a single query like described above ...