There are times where adding a second table as a SQL View makes sense. In the context of Hasura, this generally applies to when you need to apply permissions. You cannot currently use computed fields in permission access, however you can control which role can access which computed field.
Our example will contain a users
table and a posts
table. We will add a post_count
to the users
table. The user won't need access to the posts
aggregate calls for other users, we can leverage a computed field to add and allow any user to see post_count
of other users.
Additionally you can query by computed field. So in our case you could find all users that had more than 0 posts. This would not be possible with aggregate.
Overall computed fields allow you to extend a table schema where a user may not have permissions to access the underlying values, or you need to query/sort by the computed field.
The initial data structure will be for demonstration purposes. We have a users
table with an id
and username
. The posts
table will contain some basic information, and hold a reference to the user_id
who owns the post.
users - id - username
posts - id - title - body - user_id
Lets walk through the structure of this SQL function. The first
CREATE OR REPLACE FUNCTION
We want to create a function if it doesn't exist, otherwise we'll replace it. The benefit of this is future migrations become easier when you need to replace existing functions especially if they already existed prior to using Hasura.
CREATE OR REPLACE FUNCTION users_posts_count(user_row users)
Now we name the function called users_posts_count
name it whatever yow want. Then we name the variable for accessing the data row the function will be called with. We called it user_row
since the function will get called with an individual user row. Finally we need to specify the table that this function will apply to. In our case it will be on the users
table.
RETURNS BIGINT AS
Now we need to specify the type of value that this will return. Remember this is going into a field, so we need to specify a consistent return type. In our case it will return a BIGINT
.
$$ SELECT count(1) FROM posts WHERE user_id = user_row.id; $$
The $$
just outline where the SQL function will go. Our query will select the count of all posts where user_id
is equal to the user_row.id
that got passed in.
LANGUAGE sql STABLE;
Finally we specify that it's a STABLE function. Basically meaning it's not going to modify the database when it is called. This allows for Postgres to optimize this function.
The full function together looks like this.
CREATE OR REPLACE FUNCTION users_posts_count(user_row users) RETURNS BIGINT AS $$ SELECT count(1) FROM posts WHERE user_id = user_row.id; $$ LANGUAGE sql STABLE;
Once our function is all ready we need to apply the function as a migration to the DB. This migration will then additionally be saved off for applying to your other environments.
Go to the users
table, visit the Computed Fields
section and give the field a name. Our field name will be post_count
. Then select our function we created from the dropdown. And click Save.
query GetUsers { users(where: { post_count: { _gte: 0 } }) { post_count id username } }
query GetUsers { users(order_by: { post_count: asc }) { post_count id username } }
Computed fields add a ton of flexibility and possibilities without having to add more tables, or views. Further more as they are invoked with a single row the SQL queries that need to be written can be simpler to write rather than complex GROUP BY
queries.