12 / 27Module 1: A Rell backend with FT accountsBeginner
Basic queries
Queries are how you read data from a Chromia blockchain. You will build and test each one in turn.
Your dapp needs queries that:
- Show a user's username.
- Return the number of followers a user has.
- Return the number of users a user follows.
- Tell you whether one user follows another.
- Fetch a list of posts.
- List every user.
The steps below show how to write and use these queries in Chromia:
User and follower queries
You declare a query like a function, using the query keyword. Start with a query that returns a username that uniquely identifies the user:
query get_user_name(user_id: byte_array): text { return user @ { user_id } ("%s#%s".format(.name, .id.to_hex().sub(0, 5)));}The query looks up the user and builds a text value by joining the name with the first five characters of the ID's hex form. Next, write two queries: one that counts a user's followers and one that counts the users they follow.
query get_followers_count(user_id: byte_array): integer { return follower @ { .user == user @ { user_id } } (@sum 1);}query get_following_count(user_id: byte_array): integer { return follower @ { .follower == user @ { user_id } } (@sum 1);}The two queries differ only in how they filter the follower database query. Rell can infer the integer return type from the return statement, so you can drop it and shorten the queries:
query get_followers_count(user_id: byte_array) = follower @ { .user == user @ { user_id } } (@sum 1);To find out whether a follower entity exists, write this query:
query is_following(my_id: byte_array, your_id: byte_array) = exists(follower @? { .user.id == your_id, .follower.id == my_id });Query posts with pagination
Now you need a query that returns posts from the users a given user follows. The result set can grow large, so you add pagination to keep it manageable. Order the posts from newest to oldest.
To carry post data, define a struct named post_dto:
struct post_dto { timestamp; user: struct<user>; content: text;}It mirrors the post entity, except that the user field uses a slightly different format. A struct holds an entity's data in memory, so every field is loaded and ready to use.
Retrieve posts
The query joins tables, filters the data, and applies pagination to get the posts you want:
query get_posts( user_id: byte_array, pointer: integer, n_posts: integer): (pointer: integer, posts: list<post_dto>) { val posts = (user, follower, post) @* { user.id == user_id, follower.follower == user, post.user == follower.user } ( @sort_desc @omit post.rowid, post_dto( post.timestamp, post.content, user = post.user.to_struct() ) ) offset pointer limit n_posts; return ( pointer = pointer + posts.size(), posts = posts );}How the query works:
user_ididentifies the user whose feed you want: posts by the users they follow.- It joins the
user,follower, andposttables to collect the required data. - It sorts posts by creation timestamp, descending, so the newest come first.
- It builds a
post_dtofor each post, including a structured representation of the user. - It takes an
offsetto skip posts and alimitto cap how many are returned.
The descending sort happens in the database query so the latest posts are fetched first, but the timestamp itself is not included in the returned structure. You could paginate by timestamp instead; using an offset keeps things simpler.
Return results
The query returns a named tuple with two parts:
pointer: the index where the next query should begin.posts: a list ofpost_dtoobjects holding the fetched posts.
This gives you paginated access to posts by the users a user follows, which makes the data easier to handle and display in your dapp.
Query user list
Listing every user takes a query that reuses the name formatting from get_user_name and the pagination from get_posts:
query get_users(pointer: integer, n_users: integer) { val users = user @* {} (name = "%s#%s".format(.name, .id.to_hex().sub(0, 5)), id = .id) offset pointer limit n_users; return ( pointer = pointer + users.size(), users = users );}Test the queries
Next, confirm the queries behave correctly. Begin with a basic test for get_user_name:
function test_user_name() { rell.test.tx() .op(create_user("Alice", alice)) .run(); assert_equals(get_user_name(alice), "Alice#02466"); val users_result = get_users(0, 20); assert_equals(users_result.pointer, 1); assert_equals(users_result.users.size(), 1); assert_true(users_result.users @* {} (.name).contains("Alice#02466"));}Note that the @-operator works on lists here.
Then check the follower counts with a second test:
val charlie = rell.test.pubkeys.charlie;function test_follower_calculation() { rell.test.tx() .op(create_user("Alice", alice)) .op(create_user("Bob", bob)) .op(create_user("Charlie", charlie)) .run(); rell.test.tx() .op(follow_user(alice, bob)) .op(follow_user(alice, charlie)) .run(); assert_true(is_following(alice, bob)); assert_true(is_following(alice, charlie)); assert_equals(get_following_count(alice), 2); assert_equals(get_following_count(bob), 0); assert_equals(get_followers_count(alice), 0); assert_equals(get_followers_count(bob), 1);}What this test does:
- Creates three users:
Alice,Bob, andCharlie. - Makes
Alicefollow bothBobandCharlie. - Uses
assert_equalsto confirm the queries report the right follower and following counts.
Test pagination for posts
Finally, test post pagination. The test creates users, sets up follow relationships, and then adds posts.
function test_pagination_of_posts() { rell.test.tx() .op(create_user("Alice", alice)) .op(create_user("Bob", bob)).run(); rell.test.tx() .op(ft_auth_operation_for(alice)) .op(follow_user(bob)) .sign(alice_kp) .run(); for (i in range(5)) { rell.test.tx() .op(ft_auth_operation_for(bob)) .op(make_post("Content %d".format(i))) .sign(bob_kp) .run(); } val initial_posts = get_posts(alice, 0, 4); assert_equals(initial_posts.pointer, 4); assert_equals(initial_posts.posts.size(), 4); val last_posts = get_posts(alice, initial_posts.pointer, 4); assert_equals(last_posts.pointer, 5); assert_equals(last_posts.posts.size(), 1);}What this test does:
- Creates the users
AliceandBob. AlicefollowsBob, andBobpublishes five posts.- Fetches Alice's feed in pages of 4: the first page returns 4 posts and pointer 4, the second returns the last post and pointer 5.