Skip to content

perf(sdk): push ledger Query filters into SQL + adopt prepare_cached#400

Merged
willwashburn merged 4 commits into
mainfrom
claude/submit-pr-open-issue-mIHWj
May 10, 2026
Merged

perf(sdk): push ledger Query filters into SQL + adopt prepare_cached#400
willwashburn merged 4 commits into
mainfrom
claude/submit-pr-open-issue-mIHWj

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes #324.

Summary

crates/relayburn-sdk/src/ledger/reader.rs was loading every row of the target table into a Vec<String> and then filtering in Rust. Pure SQL predicates (since, until, session_id, source, project) now ride into a generated WHERE clause so the existing indexes (idx_turns_ts, idx_turns_session, etc.) actually do work, and the hot SELECTs use Connection::prepare_cached so burn ingest --watch's once-a-second ingest_all read no longer pays full prepare/teardown.

  • All five query verbs share a build_select_sql helper that emits a stable SQL string per filter combination — prepare_cached reuses the compiled statement.
  • Per-table column shape (nullable ts, OR-on-related_session_id, project columns) is encoded in a small TableFilters struct so the helper stays declarative.
  • Folded-stamp enrichment is the only Query predicate still evaluated in Rust — stamps live in their own table — so query_turns keeps a post-filter for that one case.
  • collect_stamps, list_user_turn_session_ids, and raw_record_jsons also moved to prepare_cached.

Test plan

  • cargo test --workspace (647 tests in relayburn-sdk + 89 across cli/ingest helpers, all green)
  • New regression tests pin the SQL semantics:
    • combined since / source / project filters on turns
    • relationships session_id matching either endpoint (session_id OR related_session_id)
    • tool_result_events rows with null ts surviving a since bound
  • cargo build --workspace

https://claude.ai/code/session_01MzV5fYE85XC1JwNn6deQ8R


Generated by Claude Code

Closes #324.

The ledger query verbs (`query_turns`, `query_compactions`,
`query_relationships`, `query_tool_result_events`, `query_user_turns`)
used to load every row of the target table into Rust and then filter in
memory. Move the `since` / `until` / `session_id` / `source` / `project`
predicates into a generated SQL `WHERE` clause so the existing indexes
(`idx_turns_ts`, `idx_turns_session`, etc.) actually do work, and switch
hot SELECTs to `prepare_cached` so `burn ingest --watch`'s once-a-second
read no longer pays full prepare/teardown.

Folded-stamp enrichment is the only Query predicate that still runs in
Rust — stamps live in their own table — so `query_turns` keeps a
post-filter for that one case.

Adds three regression tests pinning the new SQL semantics:
- combined since/source/project filters on `turns`
- relationships `session_id` matching either endpoint
- tool_result_events with null `ts` surviving a `since` bound
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a9480d5-d9aa-4052-857a-a710595ad890

📥 Commits

Reviewing files that changed from the base of the PR and between d86d588 and 24cf195.

📒 Files selected for processing (1)
  • CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Pushes most Query predicates (since/until/session_id/source/project) into SQL WHERE clauses and executes hot SELECTs with prepare_cached; deserialized rows are returned and only stamp-enrichment matching runs in Rust. Adds regression tests and a CHANGELOG entry.

Changes

Ledger Query Filter Pushdown and Prepared Statement Caching

Layer / File(s) Summary
Changelog
CHANGELOG.md
Adds an Unreleased → Changed bullet documenting relayburn-sdk pushdown of ledger query filters into SQL and prepare_cached usage.
SQL Filtering Infrastructure
crates/relayburn-sdk/src/ledger/reader.rs
Adds TableFilters, build_select_sql, and select_filtered_records to construct parameterized WHERE clauses and execute cached prepared statements.
query_turns Refactoring
crates/relayburn-sdk/src/ledger/reader.rs
query_turns builds and executes a cached SELECT with filters pushed to SQL and applies only enrichment_filter_passes in Rust.
Other Query Functions
crates/relayburn-sdk/src/ledger/reader.rs
query_compactions, query_relationships, query_tool_result_events, and query_user_turns use select_filtered_records with per-table TableFilters instead of per-row Rust predicate helpers.
Prepared Statement Caching
crates/relayburn-sdk/src/ledger/reader.rs
list_user_turn_session_ids and raw_record_jsons switch from prepare to prepare_cached.
Enrichment Filtering Helper
crates/relayburn-sdk/src/ledger/reader.rs
Adds enrichment_filter_passes to perform stamp-enrichment key/value matching previously embedded in turn_passes.
Regression Tests
crates/relayburn-sdk/src/ledger/tests.rs
Adds tests: query_turns_filters_pushed_to_sql_match_legacy_semantics, query_relationships_session_filter_matches_either_endpoint, query_tool_result_events_keeps_null_ts_rows_under_since_filter.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through rows and built a WHERE,

pushed filters down with careful care;
cached statements now bound and run,
stamps still checked beneath the sun.
A twitching nose — the queries won.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf(sdk): push ledger Query filters into SQL + adopt prepare_cached' clearly and concisely summarizes the main performance optimization changes in the PR.
Description check ✅ Passed The description provides relevant context about moving from Rust-side post-filtering to SQL WHERE clauses, adopting prepare_cached, and includes test results confirming functionality.
Linked Issues check ✅ Passed The PR successfully implements all key objectives from issue #324: pushing SQL predicates into WHERE clauses [since/until/session_id/source/project], using prepare_cached for hot SELECTs, and adding regression tests for correctness verification.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the objectives: refactoring ledger/reader.rs filtering logic, adding TableFilters struct, implementing build_select_sql helper, updating all five query verbs, and adding regression tests per issue #324.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/submit-pr-open-issue-mIHWj

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/relayburn-sdk/src/ledger/tests.rs (1)

952-962: ⚡ Quick win

Make the project regression actually distinguish project from project_key.

This fixture currently sets both columns to "burn" on every matching row, so the test still passes if the SQL regresses to checking only one side of (project = ? OR project_key = ?). Seed one row via project only and another via project_key only.

Suggested test tightening
-    t_a.project = Some("burn".into());
-    t_a.project_key = Some("burn".into());
+    t_a.project = Some("burn".into());
+    t_a.project_key = None;
     let mut t_b = make_turn("s1", "m2", "2025-01-02T00:00:00Z", 20);
-    t_b.project = Some("burn".into());
-    t_b.project_key = Some("burn".into());
+    t_b.project = None;
+    t_b.project_key = Some("burn".into());

Also applies to: 997-1005

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/relayburn-sdk/src/ledger/tests.rs` around lines 952 - 962, The test
fixture currently sets both t_a.project and t_a.project_key (and likewise t_b)
to the same value so it doesn't catch regressions that only check one column;
change the seeds so one row has only project set (e.g., set t_a.project =
Some("burn") and leave t_a.project_key = None) and another row has only
project_key set (e.g., set t_b.project_key = Some("burn") and leave t_b.project
= None), then call l.append_turns(&[t_a, t_b, t_c]).unwrap(); do the same
adjustment for the similar block around the t_a/t_b/t_c at the other occurrence
referenced (lines ~997-1005) to ensure the SQL must check both project OR
project_key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/relayburn-sdk/src/ledger/tests.rs`:
- Around line 952-962: The test fixture currently sets both t_a.project and
t_a.project_key (and likewise t_b) to the same value so it doesn't catch
regressions that only check one column; change the seeds so one row has only
project set (e.g., set t_a.project = Some("burn") and leave t_a.project_key =
None) and another row has only project_key set (e.g., set t_b.project_key =
Some("burn") and leave t_b.project = None), then call l.append_turns(&[t_a, t_b,
t_c]).unwrap(); do the same adjustment for the similar block around the
t_a/t_b/t_c at the other occurrence referenced (lines ~997-1005) to ensure the
SQL must check both project OR project_key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ec8fcc5-3adf-49a2-9c4d-68a7735d9010

📥 Commits

Reviewing files that changed from the base of the PR and between 7da0599 and b658ee4.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • crates/relayburn-sdk/src/ledger/reader.rs
  • crates/relayburn-sdk/src/ledger/tests.rs

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

claude added 2 commits May 9, 2026 03:20
CodeRabbit nit on #400: the original fixture seeded `project` and
`project_key` to the same value on every matching row, so the test
would pass even if the SQL regressed to checking only one of the two
columns. Seed one row via `project` only and another via `project_key`
only so a single-column SQL bug fails the test.
@willwashburn

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

…-issue-mIHWj

# Conflicts:
#	CHANGELOG.md

Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>

Copilot AI commented May 10, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved and merged latest origin/main into this branch; conflict in CHANGELOG.md is fixed in commit 24cf195.

@willwashburn willwashburn merged commit 36ca53d into main May 10, 2026
11 checks passed
@willwashburn willwashburn deleted the claude/submit-pr-open-issue-mIHWj branch May 10, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rust perf: push ledger Query filters into SQL + adopt prepare_cached

3 participants