perf(me): optimize mentions query with materialized CTEs scoped to candidates
The `query_mentioned_in` SQL previously joined notes directly against the full issues/merge_requests tables, with per-row subqueries for author/assignee/reviewer exclusion. On large databases this produced pathological query plans where SQLite scanned the entire notes table before filtering to relevant entities. Refactor into a dedicated `build_mentioned_in_sql()` builder that: 1. Pre-filters candidate issues and MRs into MATERIALIZED CTEs (state open OR recently closed, not authored by user, not assigned/reviewing). This narrows the working set before any notes join. 2. Computes note timestamps (my_ts, others_ts, any_ts) as separate MATERIALIZED CTEs scoped to candidate entities only, rather than scanning all notes. 3. Joins mention-bearing notes against the pre-filtered candidates, avoiding the full-table scans. Also adds a test verifying that authored issues are excluded from the mentions results, and a unit test asserting all four CTEs are materialized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -880,6 +880,21 @@ fn mentioned_in_excludes_assigned_issue() {
|
||||
assert!(results.is_empty(), "should exclude assigned issues");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mentioned_in_excludes_authored_issue() {
|
||||
let conn = setup_test_db();
|
||||
insert_project(&conn, 1, "group/repo");
|
||||
insert_issue(&conn, 10, 1, 42, "alice"); // alice IS author
|
||||
let disc_id = 100;
|
||||
insert_discussion(&conn, disc_id, 1, None, Some(10));
|
||||
let t = now_ms() - 1000;
|
||||
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
|
||||
|
||||
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
|
||||
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
|
||||
assert!(results.is_empty(), "should exclude authored issues");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mentioned_in_finds_mention_on_non_authored_mr() {
|
||||
let conn = setup_test_db();
|
||||
@@ -1093,6 +1108,27 @@ fn mentioned_in_rejects_false_positive_email() {
|
||||
|
||||
// ─── Helper Tests ──────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn mentioned_in_sql_materializes_core_ctes() {
|
||||
let sql = build_mentioned_in_sql("");
|
||||
assert!(
|
||||
sql.contains("candidate_issues AS MATERIALIZED"),
|
||||
"candidate_issues should be materialized"
|
||||
);
|
||||
assert!(
|
||||
sql.contains("candidate_mrs AS MATERIALIZED"),
|
||||
"candidate_mrs should be materialized"
|
||||
);
|
||||
assert!(
|
||||
sql.contains("note_ts_issue AS MATERIALIZED"),
|
||||
"note_ts_issue should be materialized"
|
||||
);
|
||||
assert!(
|
||||
sql.contains("note_ts_mr AS MATERIALIZED"),
|
||||
"note_ts_mr should be materialized"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_attention_state_all_variants() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -789,6 +789,87 @@ struct RawMentionRow {
|
||||
mention_body: String,
|
||||
}
|
||||
|
||||
fn build_mentioned_in_sql(project_clause: &str) -> String {
|
||||
format!(
|
||||
"WITH candidate_issues AS MATERIALIZED (
|
||||
SELECT i.id, i.iid, i.title, p.path_with_namespace, i.state,
|
||||
i.updated_at, i.web_url
|
||||
FROM issues i
|
||||
JOIN projects p ON i.project_id = p.id
|
||||
WHERE (i.state = 'opened' OR (i.state = 'closed' AND i.updated_at > ?2))
|
||||
AND (i.author_username IS NULL OR i.author_username != ?1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM issue_assignees ia
|
||||
WHERE ia.issue_id = i.id AND ia.username = ?1
|
||||
)
|
||||
{project_clause}
|
||||
),
|
||||
candidate_mrs AS MATERIALIZED (
|
||||
SELECT m.id, m.iid, m.title, p.path_with_namespace, m.state,
|
||||
m.updated_at, m.web_url
|
||||
FROM merge_requests m
|
||||
JOIN projects p ON m.project_id = p.id
|
||||
WHERE (m.state = 'opened'
|
||||
OR (m.state IN ('merged', 'closed') AND m.updated_at > ?2))
|
||||
AND m.author_username != ?1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM mr_reviewers rv
|
||||
WHERE rv.merge_request_id = m.id AND rv.username = ?1
|
||||
)
|
||||
{project_clause}
|
||||
),
|
||||
note_ts_issue AS MATERIALIZED (
|
||||
SELECT d.issue_id,
|
||||
MAX(CASE WHEN n.author_username = ?1 THEN n.created_at END) AS my_ts,
|
||||
MAX(CASE WHEN n.author_username != ?1 THEN n.created_at END) AS others_ts,
|
||||
MAX(n.created_at) AS any_ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN candidate_issues ci ON ci.id = d.issue_id
|
||||
WHERE n.is_system = 0
|
||||
GROUP BY d.issue_id
|
||||
),
|
||||
note_ts_mr AS MATERIALIZED (
|
||||
SELECT d.merge_request_id,
|
||||
MAX(CASE WHEN n.author_username = ?1 THEN n.created_at END) AS my_ts,
|
||||
MAX(CASE WHEN n.author_username != ?1 THEN n.created_at END) AS others_ts,
|
||||
MAX(n.created_at) AS any_ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN candidate_mrs cm ON cm.id = d.merge_request_id
|
||||
WHERE n.is_system = 0
|
||||
GROUP BY d.merge_request_id
|
||||
)
|
||||
-- Issue mentions (scoped to candidate entities only)
|
||||
SELECT 'issue', ci.iid, ci.title, ci.path_with_namespace, ci.state,
|
||||
ci.updated_at, ci.web_url,
|
||||
nt.my_ts, nt.others_ts, nt.any_ts,
|
||||
n.body
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN candidate_issues ci ON ci.id = d.issue_id
|
||||
LEFT JOIN note_ts_issue nt ON nt.issue_id = ci.id
|
||||
WHERE n.is_system = 0
|
||||
AND n.author_username != ?1
|
||||
AND LOWER(n.body) LIKE '%@' || LOWER(?1) || '%'
|
||||
UNION ALL
|
||||
-- MR mentions (scoped to candidate entities only)
|
||||
SELECT 'mr', cm.iid, cm.title, cm.path_with_namespace, cm.state,
|
||||
cm.updated_at, cm.web_url,
|
||||
nt.my_ts, nt.others_ts, nt.any_ts,
|
||||
n.body
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN candidate_mrs cm ON cm.id = d.merge_request_id
|
||||
LEFT JOIN note_ts_mr nt ON nt.merge_request_id = cm.id
|
||||
WHERE n.is_system = 0
|
||||
AND n.author_username != ?1
|
||||
AND LOWER(n.body) LIKE '%@' || LOWER(?1) || '%'
|
||||
ORDER BY 6 DESC
|
||||
LIMIT 500",
|
||||
)
|
||||
}
|
||||
|
||||
/// Query issues and MRs where the user is @mentioned but not assigned/authored/reviewing.
|
||||
///
|
||||
/// Includes open items unconditionally, plus recently-closed/merged items
|
||||
@@ -802,77 +883,8 @@ pub fn query_mentioned_in(
|
||||
recency_cutoff_ms: i64,
|
||||
) -> Result<Vec<MeMention>> {
|
||||
let project_clause = build_project_clause_at("p.id", project_ids, 3);
|
||||
|
||||
// CTE: note timestamps per issue (for attention state computation)
|
||||
// CTE: note timestamps per MR
|
||||
// Then UNION ALL of issue mentions + MR mentions
|
||||
let sql = format!(
|
||||
"WITH note_ts_issue AS (
|
||||
SELECT d.issue_id,
|
||||
MAX(CASE WHEN n.author_username = ?1 THEN n.created_at END) AS my_ts,
|
||||
MAX(CASE WHEN n.author_username != ?1 THEN n.created_at END) AS others_ts,
|
||||
MAX(n.created_at) AS any_ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
WHERE n.is_system = 0 AND d.issue_id IS NOT NULL
|
||||
GROUP BY d.issue_id
|
||||
),
|
||||
note_ts_mr AS (
|
||||
SELECT d.merge_request_id,
|
||||
MAX(CASE WHEN n.author_username = ?1 THEN n.created_at END) AS my_ts,
|
||||
MAX(CASE WHEN n.author_username != ?1 THEN n.created_at END) AS others_ts,
|
||||
MAX(n.created_at) AS any_ts
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
WHERE n.is_system = 0 AND d.merge_request_id IS NOT NULL
|
||||
GROUP BY d.merge_request_id
|
||||
)
|
||||
-- Issue mentions
|
||||
SELECT 'issue', i.iid, i.title, p.path_with_namespace, i.state,
|
||||
i.updated_at, i.web_url,
|
||||
nt.my_ts, nt.others_ts, nt.any_ts,
|
||||
n.body
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN issues i ON d.issue_id = i.id
|
||||
JOIN projects p ON i.project_id = p.id
|
||||
LEFT JOIN note_ts_issue nt ON nt.issue_id = i.id
|
||||
WHERE n.is_system = 0
|
||||
AND n.author_username != ?1
|
||||
AND d.issue_id IS NOT NULL
|
||||
AND LOWER(n.body) LIKE '%@' || LOWER(?1) || '%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM issue_assignees ia
|
||||
WHERE ia.issue_id = d.issue_id AND ia.username = ?1
|
||||
)
|
||||
AND (i.state = 'opened' OR (i.state = 'closed' AND i.updated_at > ?2))
|
||||
{project_clause}
|
||||
UNION ALL
|
||||
-- MR mentions
|
||||
SELECT 'mr', m.iid, m.title, p.path_with_namespace, m.state,
|
||||
m.updated_at, m.web_url,
|
||||
nt.my_ts, nt.others_ts, nt.any_ts,
|
||||
n.body
|
||||
FROM notes n
|
||||
JOIN discussions d ON n.discussion_id = d.id
|
||||
JOIN merge_requests m ON d.merge_request_id = m.id
|
||||
JOIN projects p ON m.project_id = p.id
|
||||
LEFT JOIN note_ts_mr nt ON nt.merge_request_id = m.id
|
||||
WHERE n.is_system = 0
|
||||
AND n.author_username != ?1
|
||||
AND d.merge_request_id IS NOT NULL
|
||||
AND LOWER(n.body) LIKE '%@' || LOWER(?1) || '%'
|
||||
AND m.author_username != ?1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM mr_reviewers rv
|
||||
WHERE rv.merge_request_id = d.merge_request_id AND rv.username = ?1
|
||||
)
|
||||
AND (m.state = 'opened'
|
||||
OR (m.state IN ('merged', 'closed') AND m.updated_at > ?2))
|
||||
{project_clause}
|
||||
ORDER BY 6 DESC
|
||||
LIMIT 500",
|
||||
);
|
||||
// Materialized CTEs avoid pathological query plans for project-scoped mentions.
|
||||
let sql = build_mentioned_in_sql(&project_clause);
|
||||
|
||||
let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
|
||||
params.push(Box::new(username.to_string()));
|
||||
|
||||
Reference in New Issue
Block a user