diff --git a/parakeet/src/entities/post.rs b/parakeet/src/entities/post.rs --- a/parakeet/src/entities/post.rs +++ b/parakeet/src/entities/post.rs @@ -902,14 +902,60 @@ use diesel_async::RunQueryDsl; use diesel::sql_types::{Integer, BigInt}; - let rows: Vec = diesel::sql_query(include_str!("../sql/thread_parent_by_id.sql")) - .bind::(target_actor_id) - .bind::(target_rkey) - .bind::(height) - .bind::(root_actor_id) - .bind::(root_rkey) - .load(&mut conn) - .await?; + // Recursive CTE to walk up the thread parent chain using natural primary keys + let rows: Vec = diesel::sql_query( + r#" + WITH RECURSIVE parents AS ( + -- Base case: parent of the target post + SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + 0 as depth + FROM posts target_p + INNER JOIN posts p ON target_p.parent_post_actor_id = p.actor_id + AND target_p.parent_post_rkey = p.rkey + WHERE target_p.actor_id = $1 + AND target_p.rkey = $2 + AND p.violates_threadgate = FALSE + -- Filter by root to use idx_posts_root and limit search space + AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5 + OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself + + UNION ALL + + -- Recursive case: parents of parents + SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + parents.depth + 1 + FROM parents + INNER JOIN posts p ON parents.parent_post_actor_id = p.actor_id + AND parents.parent_post_rkey = p.rkey + WHERE parents.depth < $3 + AND p.violates_threadgate = FALSE + -- Filter by root to use idx_posts_root and limit search space + AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5 + OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself + ) + SELECT * FROM parents + ORDER BY depth DESC + "# + ) + .bind::(target_actor_id) + .bind::(target_rkey) + .bind::(height) + .bind::(root_actor_id) + .bind::(root_rkey) + .load(&mut conn) + .await?; Ok(rows.into_iter().map(|row| ThreadItem { actor_id: row.actor_id, @@ -1171,12 +1217,52 @@ use diesel_async::RunQueryDsl; use diesel::sql_types::{Integer, BigInt}; - let rows: Vec = diesel::sql_query(include_str!("../sql/thread.sql")) - .bind::(target_actor_id) - .bind::(target_rkey) - .bind::(depth) - .load(&mut conn) - .await?; + // Recursive CTE to traverse thread replies using natural primary keys + let rows: Vec = diesel::sql_query( + r#" + WITH RECURSIVE thread AS ( + -- Base case: direct replies to the target post + SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + 1 as depth + FROM posts target_p + INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id + AND p.parent_post_rkey = target_p.rkey + WHERE target_p.actor_id = $1 + AND target_p.rkey = $2 + AND p.violates_threadgate = FALSE + + UNION ALL + + -- Recursive case: replies to replies + SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + thread.depth + 1 + FROM thread + INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id + AND p.parent_post_rkey = thread.rkey + WHERE thread.depth < $3 + AND p.violates_threadgate = FALSE + ) + SELECT * FROM thread + ORDER BY depth ASC + "# + ) + .bind::(target_actor_id) + .bind::(target_rkey) + .bind::(depth) + .load(&mut conn) + .await?; Ok(rows.into_iter().map(|row| ThreadItem { actor_id: row.actor_id, @@ -1232,14 +1318,55 @@ depth: i32, } - let rows: Vec = diesel::sql_query(include_str!("../sql/thread_branching_by_id.sql")) - .bind::(target_actor_id) - .bind::(target_rkey) - .bind::(below) // $3 = max depth - .bind::(branching_factor) // $4 = branching factor - .load(&mut conn) - .await - .unwrap_or_default(); // Return empty vector for non-existent posts + // Recursive CTE with branching factor limit using natural primary keys + let rows: Vec = diesel::sql_query( + r#" + WITH RECURSIVE thread AS ( + -- Base case: direct replies to the target post + SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + 1 as depth + FROM posts target_p + INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id + AND p.parent_post_rkey = target_p.rkey + WHERE target_p.actor_id = $1 + AND target_p.rkey = $2 + AND p.violates_threadgate = FALSE + + UNION ALL + + -- Recursive case: replies to replies with branching limit + (SELECT + p.actor_id, + p.rkey, + p.parent_post_actor_id, + p.parent_post_rkey, + p.root_post_actor_id, + p.root_post_rkey, + thread.depth + 1 + FROM thread + INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id + AND p.parent_post_rkey = thread.rkey + WHERE thread.depth < $3 + AND p.violates_threadgate = FALSE + LIMIT $4) + ) + SELECT * FROM thread + ORDER BY depth ASC + "# + ) + .bind::(target_actor_id) + .bind::(target_rkey) + .bind::(below) // $3 = max depth + .bind::(branching_factor) // $4 = branching factor + .load(&mut conn) + .await + .unwrap_or_default(); // Return empty vector for non-existent posts Ok(rows.into_iter().map(|row| ThreadItem { actor_id: row.actor_id, diff --git a/parakeet/src/sql/get_list_members.sql b/parakeet/src/sql/get_list_members.sql deleted file mode 100644 --- a/parakeet/src/sql/get_list_members.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Fetch members of specific lists (for viewer state list checks) --- Parameters: --- $1 = array of list_owner_actor_ids (integer[]) --- $2 = array of list_rkeys (text[]) --- Returns which actors are in which lists -SELECT - li.list_owner_actor_id, - li.list_rkey, - ARRAY_AGG(li.subject_actor_id) as member_ids -FROM list_items li -WHERE - li.list_owner_actor_id = ANY($1) - AND li.list_rkey = ANY($2) -GROUP BY li.list_owner_actor_id, li.list_rkey; \ No newline at end of file diff --git a/parakeet/src/sql/get_subjects_relationships.sql b/parakeet/src/sql/get_subjects_relationships.sql deleted file mode 100644 --- a/parakeet/src/sql/get_subjects_relationships.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Fetch multiple actors' relationship arrays for reverse lookups --- Parameter: $1 = array of actor_ids (integer[]) --- Only fetches the arrays needed for reverse relationship checks -SELECT - id, - did, - following, -- To check if subject follows viewer - blocks, -- To check if subject blocks viewer - list_blocks -- To check if subject has viewer in blocked lists -FROM actors -WHERE id = ANY($1); \ No newline at end of file diff --git a/parakeet/src/sql/get_viewer_actor.sql b/parakeet/src/sql/get_viewer_actor.sql deleted file mode 100644 --- a/parakeet/src/sql/get_viewer_actor.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Fetch a single actor with all arrays for viewer state caching --- Parameter: $1 = actor_id (integer) -SELECT - id, - did, - following, - blocks, - mutes, - list_blocks, - list_mutes -FROM actors -WHERE id = $1 -LIMIT 1; \ No newline at end of file diff --git a/parakeet/src/sql/list_states.sql b/parakeet/src/sql/list_states.sql deleted file mode 100644 --- a/parakeet/src/sql/list_states.sql +++ /dev/null @@ -1,47 +0,0 @@ --- Get list states (blocks, mutes) for a viewer (DENORMALIZED) --- $1 = viewer DID --- $2 = array of list dids --- $3 = array of list rkeys (text - lists use text rkeys) -with viewer as ( - select id from actors where did = $1 -), -lookup_lists as ( - select - a.did as list_did, - a.id as list_actor_id, - l.rkey as list_rkey - from lists l - inner join actors a on l.actor_id = a.id - inner join unnest($2::text[], $3::text[]) AS lookup(lookup_did, lookup_rkey) - ON a.did = lookup.lookup_did AND l.rkey = lookup.lookup_rkey -), -list_blocks_unnested as ( - select - ll.list_did, - ll.list_rkey, - va.did as block_actor_did, - (lb).rkey as block_rkey - from viewer v - inner join actors va on va.id = v.id - cross join unnest(va.list_blocks) as lb - inner join lookup_lists ll on (lb).list_actor_id = ll.list_actor_id and (lb).list_rkey = ll.list_rkey -), -list_mutes_unnested as ( - select - ll.list_did, - ll.list_rkey - from viewer v - inner join actors va on va.id = v.id - cross join unnest(va.list_mutes) as lm - inner join lookup_lists ll on (lm).list_actor_id = ll.list_actor_id and (lm).list_rkey = ll.list_rkey -) -select - ll.list_did, - ll.list_rkey, - lb.block_actor_did, - lb.block_rkey, - lm.list_did is not null as muted -from lookup_lists ll -left join list_blocks_unnested lb on ll.list_did = lb.list_did and ll.list_rkey = lb.list_rkey -left join list_mutes_unnested lm on ll.list_did = lm.list_did and ll.list_rkey = lm.list_rkey -where (lb.block_actor_did is not null or lm.list_did is not null) \ No newline at end of file diff --git a/parakeet/src/sql/profile_state.sql b/parakeet/src/sql/profile_state.sql deleted file mode 100644 --- a/parakeet/src/sql/profile_state.sql +++ /dev/null @@ -1,128 +0,0 @@ --- Profile state query using denormalized schema --- Reconstructs profile state from actors.following, actors.blocks, actors.mutes arrays --- and actors.list_blocks/list_mutes arrays --- Parameters: $1 = viewer DID, $2 = array of subject DIDs - -with viewer as ( - select id from actors where did = $1 -), -subjects as ( - select id, did from actors where did = any($2) -), --- Following relationship (return rkey bigint for encoding in Rust) --- Read from viewer's following[] array -following_cte as ( - select s.did as subject, (f).rkey as following - from viewer v - cross join lateral unnest(COALESCE( - (select following from actors where id = v.id), - ARRAY[]::follow_record[] - )) as f - inner join subjects s on (f).subject_actor_id = s.id -), --- Followed relationship (return rkey bigint for encoding in Rust) --- Read from subject's following[] array (subject follows viewer) -followed_cte as ( - select s.did as subject, (f).rkey as followed - from subjects s - cross join lateral unnest(COALESCE( - (select following from actors where id = s.id), - ARRAY[]::follow_record[] - )) as f - inner join viewer v on (f).subject_actor_id = v.id -), --- Blocking relationship (return rkey bigint for encoding in Rust) --- Read from viewer's blocks[] array -blocking_cte as ( - select s.did as subject, (b).rkey as blocking - from viewer v - cross join lateral unnest(COALESCE( - (select blocks from actors where id = v.id), - ARRAY[]::block_record[] - )) as b - inner join subjects s on (b).subject_actor_id = s.id -), --- Blocked relationship (subject has blocked viewer) --- Read from subject's blocks[] array -blocked_cte as ( - select s.did as subject, true as blocked - from subjects s - cross join lateral unnest(COALESCE( - (select blocks from actors where id = s.id), - ARRAY[]::block_record[] - )) as b - inner join viewer v on (b).subject_actor_id = v.id -), --- Muting relationship --- Read from viewer's mutes[] array -muting_cte as ( - select s.did as subject, true as muting - from viewer v - cross join lateral unnest(COALESCE( - (select mutes from actors where id = v.id), - ARRAY[]::mute_record[] - )) as m - inner join subjects s on (m).subject_actor_id = s.id -), --- List blocks (viewer has blocked subject via list) --- Viewer has blocked lists → check if subject is in those lists -vlb as ( - select s.did as subject, list_owner.did as list_owner_did, (lb).list_rkey as list_rkey - from viewer v - cross join lateral unnest(COALESCE( - (select list_blocks from actors where id = v.id), - ARRAY[]::list_block_record[] - )) as lb - inner join actors list_owner on list_owner.id = (lb).list_actor_id - inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id - and li.list_rkey = (lb).list_rkey - inner join subjects s on s.id = li.subject_actor_id -), --- List blocks reverse (subject has blocked viewer via list) --- Subject has blocked lists → check if viewer is in those lists -vlb2 as ( - select s.did as subject, true as blocked - from subjects s - cross join lateral unnest(COALESCE( - (select list_blocks from actors where id = s.id), - ARRAY[]::list_block_record[] - )) as lb - inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id - and li.list_rkey = (lb).list_rkey - inner join viewer v on v.id = li.subject_actor_id -), --- List mutes (viewer has muted subject via list) --- Viewer has muted lists → check if subject is in those lists -vlm as ( - select s.did as subject, list_owner.did as list_owner_did, (lm).list_rkey as list_rkey - from viewer v - cross join lateral unnest(COALESCE( - (select list_mutes from actors where id = v.id), - ARRAY[]::list_mute_record[] - )) as lm - inner join actors list_owner on list_owner.id = (lm).list_actor_id - inner join list_items li on li.list_owner_actor_id = (lm).list_actor_id - and li.list_rkey = (lm).list_rkey - inner join subjects s on s.id = li.subject_actor_id -) -select - $1 as did, - coalesce(s.did, following.subject, followed.subject, blocking.subject, blocked.subject, muting.subject, vlb.subject, vlb2.subject, vlm.subject) as subject, - muting.muting, - coalesce(blocked.blocked, vlb2.blocked, false) as blocked, - blocking.blocking, - following.following, - followed.followed, - vlb.list_owner_did as list_block_owner_did, - vlb.list_rkey as list_block_rkey, - vlm.list_owner_did as list_mute_owner_did, - vlm.list_rkey as list_mute_rkey -from subjects s -full join following_cte following on following.subject = s.did -full join followed_cte followed on followed.subject = s.did -full join blocking_cte blocking on blocking.subject = s.did -full join blocked_cte blocked on blocked.subject = s.did -full join muting_cte muting on muting.subject = s.did -full join vlb on vlb.subject = s.did -full join vlb2 on vlb2.subject = s.did -full join vlm on vlm.subject = s.did; diff --git a/parakeet/src/sql/profile_state_by_ids.sql b/parakeet/src/sql/profile_state_by_ids.sql deleted file mode 100644 --- a/parakeet/src/sql/profile_state_by_ids.sql +++ /dev/null @@ -1,128 +0,0 @@ --- Profile state query using actor_ids instead of DIDs (optimized for IdCache usage) --- Reconstructs profile state from actors.following, actors.blocks, actors.mutes arrays --- and actors.list_blocks/list_mutes arrays --- Parameters: $1 = viewer actor_id (integer), $2 = array of subject actor_ids (integer[]) --- --- This is an optimized version that avoids decompressing the actors table by accepting --- actor_ids directly. The caller should use IdCache to resolve DIDs to actor_ids first. - -with viewer as ( - select $1::integer as id -), -subjects as ( - select unnest($2::integer[]) as id -), --- Following relationship (return rkey bigint for encoding in Rust) --- Read from viewer's following[] array -following_cte as ( - select s.id as subject_id, (f).rkey as following - from viewer v - cross join lateral unnest(COALESCE( - (select following from actors where id = v.id), - ARRAY[]::follow_record[] - )) as f - inner join subjects s on (f).subject_actor_id = s.id -), --- Followed relationship (return rkey bigint for encoding in Rust) --- Read from subject's following[] array (subject follows viewer) -followed_cte as ( - select s.id as subject_id, (f).rkey as followed - from subjects s - cross join lateral unnest(COALESCE( - (select following from actors where id = s.id), - ARRAY[]::follow_record[] - )) as f - inner join viewer v on (f).subject_actor_id = v.id -), --- Blocking relationship (return rkey bigint for encoding in Rust) --- Read from viewer's blocks[] array -blocking_cte as ( - select s.id as subject_id, (b).rkey as blocking - from viewer v - cross join lateral unnest(COALESCE( - (select blocks from actors where id = v.id), - ARRAY[]::block_record[] - )) as b - inner join subjects s on (b).subject_actor_id = s.id -), --- Blocked relationship (subject has blocked viewer) --- Read from subject's blocks[] array -blocked_cte as ( - select s.id as subject_id, true as blocked - from subjects s - cross join lateral unnest(COALESCE( - (select blocks from actors where id = s.id), - ARRAY[]::block_record[] - )) as b - inner join viewer v on (b).subject_actor_id = v.id -), --- Muting relationship --- Read from viewer's mutes[] array -muting_cte as ( - select s.id as subject_id, true as muting - from viewer v - cross join lateral unnest(COALESCE( - (select mutes from actors where id = v.id), - ARRAY[]::mute_record[] - )) as m - inner join subjects s on (m).subject_actor_id = s.id -), --- List blocks (viewer has blocked subject via list) --- Viewer has blocked lists → check if subject is in those lists -vlb as ( - select s.id as subject_id, (lb).list_actor_id as list_owner_actor_id, (lb).list_rkey as list_rkey - from viewer v - cross join lateral unnest(COALESCE( - (select list_blocks from actors where id = v.id), - ARRAY[]::list_block_record[] - )) as lb - inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id - and li.list_rkey = (lb).list_rkey - inner join subjects s on s.id = li.subject_actor_id -), --- List blocks reverse (subject has blocked viewer via list) --- Subject has blocked lists → check if viewer is in those lists -vlb2 as ( - select s.id as subject_id, true as blocked - from subjects s - cross join lateral unnest(COALESCE( - (select list_blocks from actors where id = s.id), - ARRAY[]::list_block_record[] - )) as lb - inner join list_items li on li.list_owner_actor_id = (lb).list_actor_id - and li.list_rkey = (lb).list_rkey - inner join viewer v on v.id = li.subject_actor_id -), --- List mutes (viewer has muted subject via list) --- Viewer has muted lists → check if subject is in those lists -vlm as ( - select s.id as subject_id, (lm).list_actor_id as list_owner_actor_id, (lm).list_rkey as list_rkey - from viewer v - cross join lateral unnest(COALESCE( - (select list_mutes from actors where id = v.id), - ARRAY[]::list_mute_record[] - )) as lm - inner join list_items li on li.list_owner_actor_id = (lm).list_actor_id - and li.list_rkey = (lm).list_rkey - inner join subjects s on s.id = li.subject_actor_id -) -select - s.id as subject_id, - muting.muting, - coalesce(blocked.blocked, vlb2.blocked, false) as blocked, - blocking.blocking, - following.following, - followed.followed, - vlb.list_owner_actor_id as list_block_owner_actor_id, - vlb.list_rkey as list_block_rkey, - vlm.list_owner_actor_id as list_mute_owner_actor_id, - vlm.list_rkey as list_mute_rkey -from subjects s -left join following_cte following on following.subject_id = s.id -left join followed_cte followed on followed.subject_id = s.id -left join blocking_cte blocking on blocking.subject_id = s.id -left join blocked_cte blocked on blocked.subject_id = s.id -left join muting_cte muting on muting.subject_id = s.id -left join vlb on vlb.subject_id = s.id -left join vlb2 on vlb2.subject_id = s.id -left join vlm on vlm.subject_id = s.id; diff --git a/parakeet/src/sql/thread.sql b/parakeet/src/sql/thread.sql deleted file mode 100644 --- a/parakeet/src/sql/thread.sql +++ /dev/null @@ -1,42 +0,0 @@ --- Recursive CTE to traverse thread replies --- $1 = target post actor_id (integer) --- $2 = target post rkey (bigint) --- $3 = max depth -WITH RECURSIVE thread AS ( - -- Base case: direct replies to the target post - SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, -- Already in posts table, no JOIN needed - p.root_post_actor_id, - p.root_post_rkey, -- Already in posts table, no JOIN needed - 1 as depth - FROM posts target_p - -- Join replies using natural keys (NO actors table join!) - INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id - AND p.parent_post_rkey = target_p.rkey - WHERE target_p.actor_id = $1 - AND target_p.rkey = $2 - AND p.violates_threadgate = FALSE - - UNION ALL - - -- Recursive case: replies to replies (NO actors table join!) - SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, -- Already in posts table, no JOIN needed - p.root_post_actor_id, - p.root_post_rkey, -- Already in posts table, no JOIN needed - thread.depth + 1 - FROM thread - -- Join directly on actor_id from CTE (NO actors table join!) - INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id - AND p.parent_post_rkey = thread.rkey - WHERE thread.depth < $3 - AND p.violates_threadgate = FALSE -) -SELECT * FROM thread -ORDER BY depth ASC; diff --git a/parakeet/src/sql/thread_branching_by_id.sql b/parakeet/src/sql/thread_branching_by_id.sql deleted file mode 100644 --- a/parakeet/src/sql/thread_branching_by_id.sql +++ /dev/null @@ -1,43 +0,0 @@ --- Recursive CTE with branching factor limit --- $1 = target post actor_id (integer) --- $2 = target post rkey (bigint) --- $3 = max depth --- $4 = branching factor (max children per parent) -WITH RECURSIVE thread AS ( - -- Base case: direct replies to the target post - SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, -- Already in posts table, no JOIN needed - p.root_post_actor_id, - p.root_post_rkey, -- Already in posts table, no JOIN needed - 1 as depth - FROM posts target_p - -- Join replies using natural keys (NO actors table join!) - INNER JOIN posts p ON p.parent_post_actor_id = target_p.actor_id - AND p.parent_post_rkey = target_p.rkey - WHERE target_p.actor_id = $1 - AND target_p.rkey = $2 - AND p.violates_threadgate = FALSE - - UNION ALL - - -- Recursive case: replies to replies with branching limit - (SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, - p.root_post_actor_id, - p.root_post_rkey, - thread.depth + 1 - FROM thread - INNER JOIN posts p ON p.parent_post_actor_id = thread.actor_id - AND p.parent_post_rkey = thread.rkey - WHERE thread.depth < $3 - AND p.violates_threadgate = FALSE - LIMIT $4) -) -SELECT * FROM thread -ORDER BY depth ASC; diff --git a/parakeet/src/sql/thread_parent_by_id.sql b/parakeet/src/sql/thread_parent_by_id.sql deleted file mode 100644 --- a/parakeet/src/sql/thread_parent_by_id.sql +++ /dev/null @@ -1,55 +0,0 @@ --- Recursive CTE to walk up the thread parent chain using actor IDs. --- $1 = target post actor_id (integer) --- $2 = target post rkey (bigint) --- $3 = max depth --- $4 = root_post_actor_id (integer, from target post) --- $5 = root_post_rkey (bigint, from target post) --- --- Optimizations: --- - Uses actor_id lookups directly, avoiding actors table joins --- - Filters by root_post to use idx_posts_root index --- - Uses denormalized rkey columns instead of JOINs (270x faster!) -WITH RECURSIVE parents AS ( - -- Base case: parent of the target post - SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, -- Already in posts table, no JOIN needed - p.root_post_actor_id, - p.root_post_rkey, -- Already in posts table, no JOIN needed - 0 as depth - FROM posts target_p - -- Join parent using natural keys (NO actors table join!) - INNER JOIN posts p ON target_p.parent_post_actor_id = p.actor_id - AND target_p.parent_post_rkey = p.rkey - WHERE target_p.actor_id = $1 - AND target_p.rkey = $2 - AND p.violates_threadgate = FALSE - -- Filter by root to use idx_posts_root and limit search space - AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5 - OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself - - UNION ALL - - -- Recursive case: parents of parents (NO actors table join!) - SELECT - p.actor_id, - p.rkey, - p.parent_post_actor_id, - p.parent_post_rkey, -- Already in posts table, no JOIN needed - p.root_post_actor_id, - p.root_post_rkey, -- Already in posts table, no JOIN needed - parents.depth + 1 - FROM parents - -- Join directly on actor_id from CTE (NO actors table join!) - INNER JOIN posts p ON parents.parent_post_actor_id = p.actor_id - AND parents.parent_post_rkey = p.rkey - WHERE parents.depth < $3 - AND p.violates_threadgate = FALSE - -- Filter by root to use idx_posts_root and limit search space - AND (p.root_post_actor_id = $4 AND p.root_post_rkey = $5 - OR (p.actor_id = $4 AND p.rkey = $5)) -- Parent might be the root itself -) -SELECT * FROM parents -ORDER BY depth DESC; diff --git a/parakeet/src/sql/thread_v2_hidden_children.sql b/parakeet/src/sql/thread_v2_hidden_children.sql deleted file mode 100644 --- a/parakeet/src/sql/thread_v2_hidden_children.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Find hidden replies to a post (DENORMALIZED: reads from posts.threadgate_hidden_* arrays) --- $1 = parent post actor_id (integer) --- $2 = parent post rkey (bigint) --- $3 = root post actor_id (integer) --- $4 = root post rkey (bigint) --- --- PERFORMANCE: Avoids ALL actors table joins by using actor_ids directly --- DENORMALIZED: Checks denormalized threadgate_hidden_actor_ids/rkeys arrays on root post -SELECT p.actor_id, p.rkey -FROM posts p --- Join to root post to access its threadgate arrays -INNER JOIN posts root ON root.actor_id = $3 AND root.rkey = $4 -WHERE p.parent_post_actor_id = $1 - AND p.parent_post_rkey = $2 - -- Check if this reply is in the root post's hidden replies arrays - AND EXISTS ( - SELECT 1 - FROM unnest(root.threadgate_hidden_actor_ids, root.threadgate_hidden_rkeys) AS hidden(actor_id, rkey) - WHERE hidden.actor_id = p.actor_id - AND hidden.rkey = p.rkey - ) diff --git a/parakeet/src/sql/verification_by_ids.sql b/parakeet/src/sql/verification_by_ids.sql deleted file mode 100644 --- a/parakeet/src/sql/verification_by_ids.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Optimized verification query that uses actor_ids instead of DIDs --- This avoids decompressing the actors table by using IdCache to resolve DIDs beforehand --- --- Parameters: --- $1: subject_actor_ids (integer[]) - Array of subject actor IDs to fetch verifications for --- --- Performance: ~70-84ms → ~5-10ms (7-15x faster) --- Avoids: 2x actors table joins and DID-based filtering (which forces decompression) --- --- Note: Caller must use IdCache to: --- 1. Resolve subject DIDs → actor_ids before this query --- 2. Resolve subject_actor_id/verifier_actor_id → DIDs after this query - -SELECT - v.id, - v.actor_id, - v.rkey, - v.cid, - tid_timestamp(v.rkey) as created_at, - v.verifier_actor_id, - v.subject_actor_id, - v.handle, - v.display_name -FROM verification v -WHERE v.subject_actor_id = ANY($1)