···902902 use diesel_async::RunQueryDsl;
903903 use diesel::sql_types::{Integer, BigInt};
904904905905- let rows: Vec<ThreadParentRow> = diesel::sql_query(include_str!("../sql/thread_parent_by_id.sql"))
906906- .bind::<Integer, _>(target_actor_id)
907907- .bind::<BigInt, _>(target_rkey)
908908- .bind::<Integer, _>(height)
909909- .bind::<Integer, _>(root_actor_id)
910910- .bind::<BigInt, _>(root_rkey)
911911- .load(&mut conn)
912912- .await?;
905905+ // Recursive CTE to walk up the thread parent chain using natural primary keys
906906+ let rows: Vec<ThreadParentRow> = diesel::sql_query(
907907+ r#"
908908+ WITH RECURSIVE parents AS (
909909+ -- Base case: parent of the target post
910910+ SELECT
911911+ p.actor_id,
912912+ p.rkey,
913913+ p.parent_post_actor_id,
914914+ p.parent_post_rkey,
915915+ p.root_post_actor_id,
916916+ p.root_post_rkey,
917917+ 0 as depth
918918+ FROM posts target_p
919919+ INNER JOIN posts p ON target_p.parent_post_actor_id = p.actor_id
920920+ AND target_p.parent_post_rkey = p.rkey
921921+ WHERE target_p.actor_id = $1
922922+ AND target_p.rkey = $2
923923+ AND p.violates_threadgate = FALSE
924924+ -- Filter by root to use idx_posts_root and limit search space
925925+ AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5
926926+ OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself
927927+928928+ UNION ALL
929929+930930+ -- Recursive case: parents of parents
931931+ SELECT
932932+ p.actor_id,
933933+ p.rkey,
934934+ p.parent_post_actor_id,
935935+ p.parent_post_rkey,
936936+ p.root_post_actor_id,
937937+ p.root_post_rkey,
938938+ parents.depth + 1
939939+ FROM parents
940940+ INNER JOIN posts p ON parents.parent_post_actor_id = p.actor_id
941941+ AND parents.parent_post_rkey = p.rkey
942942+ WHERE parents.depth < $3
943943+ AND p.violates_threadgate = FALSE
944944+ -- Filter by root to use idx_posts_root and limit search space
945945+ AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5
946946+ OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself
947947+ )
948948+ SELECT * FROM parents
949949+ ORDER BY depth DESC
950950+ "#
951951+ )
952952+ .bind::<Integer, _>(target_actor_id)
953953+ .bind::<BigInt, _>(target_rkey)
954954+ .bind::<Integer, _>(height)
955955+ .bind::<Integer, _>(root_actor_id)
956956+ .bind::<BigInt, _>(root_rkey)
957957+ .load(&mut conn)
958958+ .await?;
913959914960 Ok(rows.into_iter().map(|row| ThreadItem {
915961 actor_id: row.actor_id,
···11711217 use diesel_async::RunQueryDsl;
11721218 use diesel::sql_types::{Integer, BigInt};
1173121911741174- let rows: Vec<ThreadChildRow> = diesel::sql_query(include_str!("../sql/thread.sql"))
11751175- .bind::<Integer, _>(target_actor_id)
11761176- .bind::<BigInt, _>(target_rkey)
11771177- .bind::<Integer, _>(depth)
11781178- .load(&mut conn)
11791179- .await?;
12201220+ // Recursive CTE to traverse thread replies using natural primary keys
12211221+ let rows: Vec<ThreadChildRow> = diesel::sql_query(
12221222+ r#"
12231223+ WITH RECURSIVE thread AS (
12241224+ -- Base case: direct replies to the target post
12251225+ SELECT
12261226+ p.actor_id,
12271227+ p.rkey,
12281228+ p.parent_post_actor_id,
12291229+ p.parent_post_rkey,
12301230+ p.root_post_actor_id,
12311231+ p.root_post_rkey,
12321232+ 1 as depth
12331233+ FROM posts target_p
12341234+ INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id
12351235+ AND p.parent_post_rkey = target_p.rkey
12361236+ WHERE target_p.actor_id = $1
12371237+ AND target_p.rkey = $2
12381238+ AND p.violates_threadgate = FALSE
12391239+12401240+ UNION ALL
12411241+12421242+ -- Recursive case: replies to replies
12431243+ SELECT
12441244+ p.actor_id,
12451245+ p.rkey,
12461246+ p.parent_post_actor_id,
12471247+ p.parent_post_rkey,
12481248+ p.root_post_actor_id,
12491249+ p.root_post_rkey,
12501250+ thread.depth + 1
12511251+ FROM thread
12521252+ INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id
12531253+ AND p.parent_post_rkey = thread.rkey
12541254+ WHERE thread.depth < $3
12551255+ AND p.violates_threadgate = FALSE
12561256+ )
12571257+ SELECT * FROM thread
12581258+ ORDER BY depth ASC
12591259+ "#
12601260+ )
12611261+ .bind::<Integer, _>(target_actor_id)
12621262+ .bind::<BigInt, _>(target_rkey)
12631263+ .bind::<Integer, _>(depth)
12641264+ .load(&mut conn)
12651265+ .await?;
1180126611811267 Ok(rows.into_iter().map(|row| ThreadItem {
11821268 actor_id: row.actor_id,
···12321318 depth: i32,
12331319 }
1234132012351235- let rows: Vec<ThreadChildRow> = diesel::sql_query(include_str!("../sql/thread_branching_by_id.sql"))
12361236- .bind::<Integer, _>(target_actor_id)
12371237- .bind::<BigInt, _>(target_rkey)
12381238- .bind::<Integer, _>(below) // $3 = max depth
12391239- .bind::<Integer, _>(branching_factor) // $4 = branching factor
12401240- .load(&mut conn)
12411241- .await
12421242- .unwrap_or_default(); // Return empty vector for non-existent posts
13211321+ // Recursive CTE with branching factor limit using natural primary keys
13221322+ let rows: Vec<ThreadChildRow> = diesel::sql_query(
13231323+ r#"
13241324+ WITH RECURSIVE thread AS (
13251325+ -- Base case: direct replies to the target post
13261326+ SELECT
13271327+ p.actor_id,
13281328+ p.rkey,
13291329+ p.parent_post_actor_id,
13301330+ p.parent_post_rkey,
13311331+ p.root_post_actor_id,
13321332+ p.root_post_rkey,
13331333+ 1 as depth
13341334+ FROM posts target_p
13351335+ INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id
13361336+ AND p.parent_post_rkey = target_p.rkey
13371337+ WHERE target_p.actor_id = $1
13381338+ AND target_p.rkey = $2
13391339+ AND p.violates_threadgate = FALSE
13401340+13411341+ UNION ALL
13421342+13431343+ -- Recursive case: replies to replies with branching limit
13441344+ (SELECT
13451345+ p.actor_id,
13461346+ p.rkey,
13471347+ p.parent_post_actor_id,
13481348+ p.parent_post_rkey,
13491349+ p.root_post_actor_id,
13501350+ p.root_post_rkey,
13511351+ thread.depth + 1
13521352+ FROM thread
13531353+ INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id
13541354+ AND p.parent_post_rkey = thread.rkey
13551355+ WHERE thread.depth < $3
13561356+ AND p.violates_threadgate = FALSE
13571357+ LIMIT $4)
13581358+ )
13591359+ SELECT * FROM thread
13601360+ ORDER BY depth ASC
13611361+ "#
13621362+ )
13631363+ .bind::<Integer, _>(target_actor_id)
13641364+ .bind::<BigInt, _>(target_rkey)
13651365+ .bind::<Integer, _>(below) // $3 = max depth
13661366+ .bind::<Integer, _>(branching_factor) // $4 = branching factor
13671367+ .load(&mut conn)
13681368+ .await
13691369+ .unwrap_or_default(); // Return empty vector for non-existent posts
1243137012441371 Ok(rows.into_iter().map(|row| ThreadItem {
12451372 actor_id: row.actor_id,
-14
parakeet/src/sql/get_list_members.sql
···11--- Fetch members of specific lists (for viewer state list checks)
22--- Parameters:
33--- $1 = array of list_owner_actor_ids (integer[])
44--- $2 = array of list_rkeys (text[])
55--- Returns which actors are in which lists
66-SELECT
77- li.list_owner_actor_id,
88- li.list_rkey,
99- ARRAY_AGG(li.subject_actor_id) as member_ids
1010-FROM list_items li
1111-WHERE
1212- li.list_owner_actor_id = ANY($1)
1313- AND li.list_rkey = ANY($2)
1414-GROUP BY li.list_owner_actor_id, li.list_rkey;
-11
parakeet/src/sql/get_subjects_relationships.sql
···11--- Fetch multiple actors' relationship arrays for reverse lookups
22--- Parameter: $1 = array of actor_ids (integer[])
33--- Only fetches the arrays needed for reverse relationship checks
44-SELECT
55- id,
66- did,
77- following, -- To check if subject follows viewer
88- blocks, -- To check if subject blocks viewer
99- list_blocks -- To check if subject has viewer in blocked lists
1010-FROM actors
1111-WHERE id = ANY($1);
-13
parakeet/src/sql/get_viewer_actor.sql
···11--- Fetch a single actor with all arrays for viewer state caching
22--- Parameter: $1 = actor_id (integer)
33-SELECT
44- id,
55- did,
66- following,
77- blocks,
88- mutes,
99- list_blocks,
1010- list_mutes
1111-FROM actors
1212-WHERE id = $1
1313-LIMIT 1;
-47
parakeet/src/sql/list_states.sql
···11--- Get list states (blocks, mutes) for a viewer (DENORMALIZED)
22--- $1 = viewer DID
33--- $2 = array of list dids
44--- $3 = array of list rkeys (text - lists use text rkeys)
55-with viewer as (
66- select id from actors where did = $1
77-),
88-lookup_lists as (
99- select
1010- a.did as list_did,
1111- a.id as list_actor_id,
1212- l.rkey as list_rkey
1313- from lists l
1414- inner join actors a on l.actor_id = a.id
1515- inner join unnest($2::text[], $3::text[]) AS lookup(lookup_did, lookup_rkey)
1616- ON a.did = lookup.lookup_did AND l.rkey = lookup.lookup_rkey
1717-),
1818-list_blocks_unnested as (
1919- select
2020- ll.list_did,
2121- ll.list_rkey,
2222- va.did as block_actor_did,
2323- (lb).rkey as block_rkey
2424- from viewer v
2525- inner join actors va on va.id = v.id
2626- cross join unnest(va.list_blocks) as lb
2727- inner join lookup_lists ll on (lb).list_actor_id = ll.list_actor_id and (lb).list_rkey = ll.list_rkey
2828-),
2929-list_mutes_unnested as (
3030- select
3131- ll.list_did,
3232- ll.list_rkey
3333- from viewer v
3434- inner join actors va on va.id = v.id
3535- cross join unnest(va.list_mutes) as lm
3636- inner join lookup_lists ll on (lm).list_actor_id = ll.list_actor_id and (lm).list_rkey = ll.list_rkey
3737-)
3838-select
3939- ll.list_did,
4040- ll.list_rkey,
4141- lb.block_actor_did,
4242- lb.block_rkey,
4343- lm.list_did is not null as muted
4444-from lookup_lists ll
4545-left join list_blocks_unnested lb on ll.list_did = lb.list_did and ll.list_rkey = lb.list_rkey
4646-left join list_mutes_unnested lm on ll.list_did = lm.list_did and ll.list_rkey = lm.list_rkey
4747-where (lb.block_actor_did is not null or lm.list_did is not null)
-128
parakeet/src/sql/profile_state.sql
···11--- Profile state query using denormalized schema
22--- Reconstructs profile state from actors.following, actors.blocks, actors.mutes arrays
33--- and actors.list_blocks/list_mutes arrays
44--- Parameters: $1 = viewer DID, $2 = array of subject DIDs
55-66-with viewer as (
77- select id from actors where did = $1
88-),
99-subjects as (
1010- select id, did from actors where did = any($2)
1111-),
1212--- Following relationship (return rkey bigint for encoding in Rust)
1313--- Read from viewer's following[] array
1414-following_cte as (
1515- select s.did as subject, (f).rkey as following
1616- from viewer v
1717- cross join lateral unnest(COALESCE(
1818- (select following from actors where id = v.id),
1919- ARRAY[]::follow_record[]
2020- )) as f
2121- inner join subjects s on (f).subject_actor_id = s.id
2222-),
2323--- Followed relationship (return rkey bigint for encoding in Rust)
2424--- Read from subject's following[] array (subject follows viewer)
2525-followed_cte as (
2626- select s.did as subject, (f).rkey as followed
2727- from subjects s
2828- cross join lateral unnest(COALESCE(
2929- (select following from actors where id = s.id),
3030- ARRAY[]::follow_record[]
3131- )) as f
3232- inner join viewer v on (f).subject_actor_id = v.id
3333-),
3434--- Blocking relationship (return rkey bigint for encoding in Rust)
3535--- Read from viewer's blocks[] array
3636-blocking_cte as (
3737- select s.did as subject, (b).rkey as blocking
3838- from viewer v
3939- cross join lateral unnest(COALESCE(
4040- (select blocks from actors where id = v.id),
4141- ARRAY[]::block_record[]
4242- )) as b
4343- inner join subjects s on (b).subject_actor_id = s.id
4444-),
4545--- Blocked relationship (subject has blocked viewer)
4646--- Read from subject's blocks[] array
4747-blocked_cte as (
4848- select s.did as subject, true as blocked
4949- from subjects s
5050- cross join lateral unnest(COALESCE(
5151- (select blocks from actors where id = s.id),
5252- ARRAY[]::block_record[]
5353- )) as b
5454- inner join viewer v on (b).subject_actor_id = v.id
5555-),
5656--- Muting relationship
5757--- Read from viewer's mutes[] array
5858-muting_cte as (
5959- select s.did as subject, true as muting
6060- from viewer v
6161- cross join lateral unnest(COALESCE(
6262- (select mutes from actors where id = v.id),
6363- ARRAY[]::mute_record[]
6464- )) as m
6565- inner join subjects s on (m).subject_actor_id = s.id
6666-),
6767--- List blocks (viewer has blocked subject via list)
6868--- Viewer has blocked lists → check if subject is in those lists
6969-vlb as (
7070- select s.did as subject, list_owner.did as list_owner_did, (lb).list_rkey as list_rkey
7171- from viewer v
7272- cross join lateral unnest(COALESCE(
7373- (select list_blocks from actors where id = v.id),
7474- ARRAY[]::list_block_record[]
7575- )) as lb
7676- inner join actors list_owner on list_owner.id = (lb).list_actor_id
7777- inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id
7878- and li.list_rkey = (lb).list_rkey
7979- inner join subjects s on s.id = li.subject_actor_id
8080-),
8181--- List blocks reverse (subject has blocked viewer via list)
8282--- Subject has blocked lists → check if viewer is in those lists
8383-vlb2 as (
8484- select s.did as subject, true as blocked
8585- from subjects s
8686- cross join lateral unnest(COALESCE(
8787- (select list_blocks from actors where id = s.id),
8888- ARRAY[]::list_block_record[]
8989- )) as lb
9090- inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id
9191- and li.list_rkey = (lb).list_rkey
9292- inner join viewer v on v.id = li.subject_actor_id
9393-),
9494--- List mutes (viewer has muted subject via list)
9595--- Viewer has muted lists → check if subject is in those lists
9696-vlm as (
9797- select s.did as subject, list_owner.did as list_owner_did, (lm).list_rkey as list_rkey
9898- from viewer v
9999- cross join lateral unnest(COALESCE(
100100- (select list_mutes from actors where id = v.id),
101101- ARRAY[]::list_mute_record[]
102102- )) as lm
103103- inner join actors list_owner on list_owner.id = (lm).list_actor_id
104104- inner join list_items li on li.list_owner_actor_id = (lm).list_actor_id
105105- and li.list_rkey = (lm).list_rkey
106106- inner join subjects s on s.id = li.subject_actor_id
107107-)
108108-select
109109- $1 as did,
110110- coalesce(s.did, following.subject, followed.subject, blocking.subject, blocked.subject, muting.subject, vlb.subject, vlb2.subject, vlm.subject) as subject,
111111- muting.muting,
112112- coalesce(blocked.blocked, vlb2.blocked, false) as blocked,
113113- blocking.blocking,
114114- following.following,
115115- followed.followed,
116116- vlb.list_owner_did as list_block_owner_did,
117117- vlb.list_rkey as list_block_rkey,
118118- vlm.list_owner_did as list_mute_owner_did,
119119- vlm.list_rkey as list_mute_rkey
120120-from subjects s
121121-full join following_cte following on following.subject = s.did
122122-full join followed_cte followed on followed.subject = s.did
123123-full join blocking_cte blocking on blocking.subject = s.did
124124-full join blocked_cte blocked on blocked.subject = s.did
125125-full join muting_cte muting on muting.subject = s.did
126126-full join vlb on vlb.subject = s.did
127127-full join vlb2 on vlb2.subject = s.did
128128-full join vlm on vlm.subject = s.did;
-128
parakeet/src/sql/profile_state_by_ids.sql
···11--- Profile state query using actor_ids instead of DIDs (optimized for IdCache usage)
22--- Reconstructs profile state from actors.following, actors.blocks, actors.mutes arrays
33--- and actors.list_blocks/list_mutes arrays
44--- Parameters: $1 = viewer actor_id (integer), $2 = array of subject actor_ids (integer[])
55---
66--- This is an optimized version that avoids decompressing the actors table by accepting
77--- actor_ids directly. The caller should use IdCache to resolve DIDs to actor_ids first.
88-99-with viewer as (
1010- select $1::integer as id
1111-),
1212-subjects as (
1313- select unnest($2::integer[]) as id
1414-),
1515--- Following relationship (return rkey bigint for encoding in Rust)
1616--- Read from viewer's following[] array
1717-following_cte as (
1818- select s.id as subject_id, (f).rkey as following
1919- from viewer v
2020- cross join lateral unnest(COALESCE(
2121- (select following from actors where id = v.id),
2222- ARRAY[]::follow_record[]
2323- )) as f
2424- inner join subjects s on (f).subject_actor_id = s.id
2525-),
2626--- Followed relationship (return rkey bigint for encoding in Rust)
2727--- Read from subject's following[] array (subject follows viewer)
2828-followed_cte as (
2929- select s.id as subject_id, (f).rkey as followed
3030- from subjects s
3131- cross join lateral unnest(COALESCE(
3232- (select following from actors where id = s.id),
3333- ARRAY[]::follow_record[]
3434- )) as f
3535- inner join viewer v on (f).subject_actor_id = v.id
3636-),
3737--- Blocking relationship (return rkey bigint for encoding in Rust)
3838--- Read from viewer's blocks[] array
3939-blocking_cte as (
4040- select s.id as subject_id, (b).rkey as blocking
4141- from viewer v
4242- cross join lateral unnest(COALESCE(
4343- (select blocks from actors where id = v.id),
4444- ARRAY[]::block_record[]
4545- )) as b
4646- inner join subjects s on (b).subject_actor_id = s.id
4747-),
4848--- Blocked relationship (subject has blocked viewer)
4949--- Read from subject's blocks[] array
5050-blocked_cte as (
5151- select s.id as subject_id, true as blocked
5252- from subjects s
5353- cross join lateral unnest(COALESCE(
5454- (select blocks from actors where id = s.id),
5555- ARRAY[]::block_record[]
5656- )) as b
5757- inner join viewer v on (b).subject_actor_id = v.id
5858-),
5959--- Muting relationship
6060--- Read from viewer's mutes[] array
6161-muting_cte as (
6262- select s.id as subject_id, true as muting
6363- from viewer v
6464- cross join lateral unnest(COALESCE(
6565- (select mutes from actors where id = v.id),
6666- ARRAY[]::mute_record[]
6767- )) as m
6868- inner join subjects s on (m).subject_actor_id = s.id
6969-),
7070--- List blocks (viewer has blocked subject via list)
7171--- Viewer has blocked lists → check if subject is in those lists
7272-vlb as (
7373- select s.id as subject_id, (lb).list_actor_id as list_owner_actor_id, (lb).list_rkey as list_rkey
7474- from viewer v
7575- cross join lateral unnest(COALESCE(
7676- (select list_blocks from actors where id = v.id),
7777- ARRAY[]::list_block_record[]
7878- )) as lb
7979- inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id
8080- and li.list_rkey = (lb).list_rkey
8181- inner join subjects s on s.id = li.subject_actor_id
8282-),
8383--- List blocks reverse (subject has blocked viewer via list)
8484--- Subject has blocked lists → check if viewer is in those lists
8585-vlb2 as (
8686- select s.id as subject_id, true as blocked
8787- from subjects s
8888- cross join lateral unnest(COALESCE(
8989- (select list_blocks from actors where id = s.id),
9090- ARRAY[]::list_block_record[]
9191- )) as lb
9292- inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id
9393- and li.list_rkey = (lb).list_rkey
9494- inner join viewer v on v.id = li.subject_actor_id
9595-),
9696--- List mutes (viewer has muted subject via list)
9797--- Viewer has muted lists → check if subject is in those lists
9898-vlm as (
9999- select s.id as subject_id, (lm).list_actor_id as list_owner_actor_id, (lm).list_rkey as list_rkey
100100- from viewer v
101101- cross join lateral unnest(COALESCE(
102102- (select list_mutes from actors where id = v.id),
103103- ARRAY[]::list_mute_record[]
104104- )) as lm
105105- inner join list_items li on li.list_owner_actor_id = (lm).list_actor_id
106106- and li.list_rkey = (lm).list_rkey
107107- inner join subjects s on s.id = li.subject_actor_id
108108-)
109109-select
110110- s.id as subject_id,
111111- muting.muting,
112112- coalesce(blocked.blocked, vlb2.blocked, false) as blocked,
113113- blocking.blocking,
114114- following.following,
115115- followed.followed,
116116- vlb.list_owner_actor_id as list_block_owner_actor_id,
117117- vlb.list_rkey as list_block_rkey,
118118- vlm.list_owner_actor_id as list_mute_owner_actor_id,
119119- vlm.list_rkey as list_mute_rkey
120120-from subjects s
121121-left join following_cte following on following.subject_id = s.id
122122-left join followed_cte followed on followed.subject_id = s.id
123123-left join blocking_cte blocking on blocking.subject_id = s.id
124124-left join blocked_cte blocked on blocked.subject_id = s.id
125125-left join muting_cte muting on muting.subject_id = s.id
126126-left join vlb on vlb.subject_id = s.id
127127-left join vlb2 on vlb2.subject_id = s.id
128128-left join vlm on vlm.subject_id = s.id;
-42
parakeet/src/sql/thread.sql
···11--- Recursive CTE to traverse thread replies
22--- $1 = target post actor_id (integer)
33--- $2 = target post rkey (bigint)
44--- $3 = max depth
55-WITH RECURSIVE thread AS (
66- -- Base case: direct replies to the target post
77- SELECT
88- p.actor_id,
99- p.rkey,
1010- p.parent_post_actor_id,
1111- p.parent_post_rkey, -- Already in posts table, no JOIN needed
1212- p.root_post_actor_id,
1313- p.root_post_rkey, -- Already in posts table, no JOIN needed
1414- 1 as depth
1515- FROM posts target_p
1616- -- Join replies using natural keys (NO actors table join!)
1717- INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id
1818- AND p.parent_post_rkey = target_p.rkey
1919- WHERE target_p.actor_id = $1
2020- AND target_p.rkey = $2
2121- AND p.violates_threadgate = FALSE
2222-2323- UNION ALL
2424-2525- -- Recursive case: replies to replies (NO actors table join!)
2626- SELECT
2727- p.actor_id,
2828- p.rkey,
2929- p.parent_post_actor_id,
3030- p.parent_post_rkey, -- Already in posts table, no JOIN needed
3131- p.root_post_actor_id,
3232- p.root_post_rkey, -- Already in posts table, no JOIN needed
3333- thread.depth + 1
3434- FROM thread
3535- -- Join directly on actor_id from CTE (NO actors table join!)
3636- INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id
3737- AND p.parent_post_rkey = thread.rkey
3838- WHERE thread.depth < $3
3939- AND p.violates_threadgate = FALSE
4040-)
4141-SELECT * FROM thread
4242-ORDER BY depth ASC;
-43
parakeet/src/sql/thread_branching_by_id.sql
···11--- Recursive CTE with branching factor limit
22--- $1 = target post actor_id (integer)
33--- $2 = target post rkey (bigint)
44--- $3 = max depth
55--- $4 = branching factor (max children per parent)
66-WITH RECURSIVE thread AS (
77- -- Base case: direct replies to the target post
88- SELECT
99- p.actor_id,
1010- p.rkey,
1111- p.parent_post_actor_id,
1212- p.parent_post_rkey, -- Already in posts table, no JOIN needed
1313- p.root_post_actor_id,
1414- p.root_post_rkey, -- Already in posts table, no JOIN needed
1515- 1 as depth
1616- FROM posts target_p
1717- -- Join replies using natural keys (NO actors table join!)
1818- INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id
1919- AND p.parent_post_rkey = target_p.rkey
2020- WHERE target_p.actor_id = $1
2121- AND target_p.rkey = $2
2222- AND p.violates_threadgate = FALSE
2323-2424- UNION ALL
2525-2626- -- Recursive case: replies to replies with branching limit
2727- (SELECT
2828- p.actor_id,
2929- p.rkey,
3030- p.parent_post_actor_id,
3131- p.parent_post_rkey,
3232- p.root_post_actor_id,
3333- p.root_post_rkey,
3434- thread.depth + 1
3535- FROM thread
3636- INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id
3737- AND p.parent_post_rkey = thread.rkey
3838- WHERE thread.depth < $3
3939- AND p.violates_threadgate = FALSE
4040- LIMIT $4)
4141-)
4242-SELECT * FROM thread
4343-ORDER BY depth ASC;
-55
parakeet/src/sql/thread_parent_by_id.sql
···11--- Recursive CTE to walk up the thread parent chain using actor IDs.
22--- $1 = target post actor_id (integer)
33--- $2 = target post rkey (bigint)
44--- $3 = max depth
55--- $4 = root_post_actor_id (integer, from target post)
66--- $5 = root_post_rkey (bigint, from target post)
77---
88--- Optimizations:
99--- - Uses actor_id lookups directly, avoiding actors table joins
1010--- - Filters by root_post to use idx_posts_root index
1111--- - Uses denormalized rkey columns instead of JOINs (270x faster!)
1212-WITH RECURSIVE parents AS (
1313- -- Base case: parent of the target post
1414- SELECT
1515- p.actor_id,
1616- p.rkey,
1717- p.parent_post_actor_id,
1818- p.parent_post_rkey, -- Already in posts table, no JOIN needed
1919- p.root_post_actor_id,
2020- p.root_post_rkey, -- Already in posts table, no JOIN needed
2121- 0 as depth
2222- FROM posts target_p
2323- -- Join parent using natural keys (NO actors table join!)
2424- INNER JOIN posts p ON target_p.parent_post_actor_id = p.actor_id
2525- AND target_p.parent_post_rkey = p.rkey
2626- WHERE target_p.actor_id = $1
2727- AND target_p.rkey = $2
2828- AND p.violates_threadgate = FALSE
2929- -- Filter by root to use idx_posts_root and limit search space
3030- AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5
3131- OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself
3232-3333- UNION ALL
3434-3535- -- Recursive case: parents of parents (NO actors table join!)
3636- SELECT
3737- p.actor_id,
3838- p.rkey,
3939- p.parent_post_actor_id,
4040- p.parent_post_rkey, -- Already in posts table, no JOIN needed
4141- p.root_post_actor_id,
4242- p.root_post_rkey, -- Already in posts table, no JOIN needed
4343- parents.depth + 1
4444- FROM parents
4545- -- Join directly on actor_id from CTE (NO actors table join!)
4646- INNER JOIN posts p ON parents.parent_post_actor_id = p.actor_id
4747- AND parents.parent_post_rkey = p.rkey
4848- WHERE parents.depth < $3
4949- AND p.violates_threadgate = FALSE
5050- -- Filter by root to use idx_posts_root and limit search space
5151- AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5
5252- OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself
5353-)
5454-SELECT * FROM parents
5555-ORDER BY depth DESC;
-21
parakeet/src/sql/thread_v2_hidden_children.sql
···11--- Find hidden replies to a post (DENORMALIZED: reads from posts.threadgate_hidden_* arrays)
22--- $1 = parent post actor_id (integer)
33--- $2 = parent post rkey (bigint)
44--- $3 = root post actor_id (integer)
55--- $4 = root post rkey (bigint)
66---
77--- PERFORMANCE: Avoids ALL actors table joins by using actor_ids directly
88--- DENORMALIZED: Checks denormalized threadgate_hidden_actor_ids/rkeys arrays on root post
99-SELECT p.actor_id, p.rkey
1010-FROM posts p
1111--- Join to root post to access its threadgate arrays
1212-INNER JOIN posts root ON root.actor_id = $3 AND root.rkey = $4
1313-WHERE p.parent_post_actor_id = $1
1414- AND p.parent_post_rkey = $2
1515- -- Check if this reply is in the root post's hidden replies arrays
1616- AND EXISTS (
1717- SELECT 1
1818- FROM unnest(root.threadgate_hidden_actor_ids, root.threadgate_hidden_rkeys) AS hidden(actor_id, rkey)
1919- WHERE hidden.actor_id = p.actor_id
2020- AND hidden.rkey = p.rkey
2121- )
-25
parakeet/src/sql/verification_by_ids.sql
···11--- Optimized verification query that uses actor_ids instead of DIDs
22--- This avoids decompressing the actors table by using IdCache to resolve DIDs beforehand
33---
44--- Parameters:
55--- $1: subject_actor_ids (integer[]) - Array of subject actor IDs to fetch verifications for
66---
77--- Performance: ~70-84ms → ~5-10ms (7-15x faster)
88--- Avoids: 2x actors table joins and DID-based filtering (which forces decompression)
99---
1010--- Note: Caller must use IdCache to:
1111--- 1. Resolve subject DIDs → actor_ids before this query
1212--- 2. Resolve subject_actor_id/verifier_actor_id → DIDs after this query
1313-1414-SELECT
1515- v.id,
1616- v.actor_id,
1717- v.rkey,
1818- v.cid,
1919- tid_timestamp(v.rkey) as created_at,
2020- v.verifier_actor_id,
2121- v.subject_actor_id,
2222- v.handle,
2323- v.display_name
2424-FROM verification v
2525-WHERE v.subject_actor_id = ANY($1)