graphql-engine/rfcs/computed-fields-filters-perms-orderby.md

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

179 lines
4.6 KiB
Markdown
Raw Normal View History

Note: This RFC assumes computed fields for **Postgres** backend.
OSS issue: https://github.com/hasura/graphql-engine/issues/3772
Before we proceed to the specification, we shall address the known limitation
### Limitation
All the undermentioned implementations assumes computed field function has table row input and/or
session (`json` type) input arguments. But we support computed fields with functions having multiple arguments.
Input values for additional arguments are provided via GraphQL field [arguments](https://spec.graphql.org/June2018/#sec-Language.Arguments).
For example, defining `get_articles` computed field for `author` table with `get_articles(table_input author, search text)` SQL
function to return author's articles containing `search` keyword.
```graphql
query {
author {
id
name
get_articles(search: "Covid 19"){
title
content
}
}
}
```
It is complex to support such additional function inputs via GraphQL arguments.
So, here I'm assuming the following limitation:
**Allow filter/order by/permission for computed fields with no input arguments other than table row and session json input types**
### Filter
Reference OSS ticket: https://github.com/hasura/graphql-engine/issues/7100
Currently, we only support table columns and relationships (object and array) to filter rows in any GraphQL query. For example, to fetch an author whose `id` is `1` the query would be (using column in filter)
```graphql
query {
authors(where: {id: {_eq: 1}}){
id
first_name
last_name
}
}
```
Similarly, to fetch articles whose `author`'s `first_name` is 'Bob', the query would be (using object relation in filter)
```graphql
query {
articles(where: {author: {first_name: {_eq: "Bob"}}}){
id
title
}
}
```
Now, I defined a computed field `full_name` to the `author` table using the following SQL function
```sql
CREATE OR REPLACE FUNCTION public.full_name(author_table author)
RETURNS text
LANGUAGE sql
STABLE
AS $function$
SELECT author_table.first_name || ' ' || author_table.last_name
$function$
```
I should able to fetch an author whose `full_name` is 'Bob Morley'
```graphql
query {
authors(where: {full_name: {_eq: "Bob Morley"}}){
id
full_name
}
}
```
The above query isn't possible currently and hence, we need to support computed fields in filter expression (`where` clause).
#### Approach
```sql
SELECT "base".* FROM "authors" AS "base" WHERE "full_name"("base") = 'Bob Morley'
```
Simply, our approach is to use the SQL function in the `WHERE` expression of the generated SQL. So, aforementioned GraphQL query
translates to
```sql
SELECT
coalesce(json_agg("root"), '[]') AS "root"
FROM
(
SELECT
row_to_json(
(
SELECT
"_2_e"
FROM
(
SELECT
"_1_root.base"."first_name" AS "first_name",
"_1_root.base"."last_name" AS "last_name",
"public"."user_full_name"("_1_root.base") AS "full_name"
) AS "_2_e"
)
) AS "root"
FROM
(
SELECT
*
FROM
"public"."user" AS "_0_base"
WHERE
(
("public"."user_full_name"("_0_base".*)) = (('Bob Morley') :: text)
)
) AS "_1_root.base"
) AS "_3_root"
```
### Permission
Reference OSS ticket: https://github.com/hasura/graphql-engine/issues/7102
Just like using computed fields in `where` expression, similarly we need have an option to include computed fields in permission
`check` and `filter` expression
For example, I need to define a select permission on `author` table whose `full_name` is like 'Bob'
```json
- type: create_select_permission
args:
table:
name: author
role: user
permission:
columns: '*'
filter:
full_name:
_like: 'Bob'
```
#### Approach
Similar to `filtering` approach, we generate `where` expression with the `filter`/`check` expression provided
in the permission metadata definition
### Order by
Reference OSS ticket: https://github.com/hasura/graphql-engine/issues/7103
Enable using computed fields in order by expresssion. For example, fetch authors ordering by `full_name`
```graphql
query {
authors(order_by: {full_name: desc}){
id
first_name
}
}
```
#### Approach
We extract computed fields with proper aliases and use the same in the `ORDER BY` SQL expression.
The generated SQL will look like
```sql
SELECT
computed_field_function("table_alias") AS "computed_field_alias"
(SELECT * FROM our_table) AS "table_alias"
ORDER BY
"computed_field_alias" DESC
```