Fetch an entire discussion page in one D1 query - #67
Merged
Conversation
GetPost issued five awaited queries in series - the post row, a COUNT over bbs_reply, the board name, the lock row, and finally the page of replies. On Workers each is a separate round trip to D1, so opening a discussion cost five times the network latency no matter how little data came back. Collapse them into a single statement built from two CTEs: `post` (the post row left-joined to bbs_board and bbs_lock) and `page` (the fifteen replies for the requested page), cross-joined so every row carries the post metadata alongside one reply, with the total reply count as a scalar subquery. The page's LIMIT/OFFSET has to be bound before PageCount is known, so the offset is clamped at zero and the rows are discarded when the range check rejects the page. Responses are unchanged, including the out-of-range and empty-discussion cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Source/Process.ts">
<violation number="1" location="Source/Process.ts:679">
P2: A D1 failure now becomes the generic `服务器运行错误` response instead of the existing database-query failure Result. Preserve the endpoint's error contract by catching this direct `all()` failure and throwing/returning the same `数据库查询失败` Result shape.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // offset is clamped here and the rows are simply discarded when the | ||
| // range check below rejects the page. | ||
| const Offset = Math.max(0, (Data["Page"] - 1) * 15); | ||
| const Rows: Array<Record<string, any>> = (await this.RawDatabase.prepare( |
Contributor
There was a problem hiding this comment.
P2: A D1 failure now becomes the generic 服务器运行错误 response instead of the existing database-query failure Result. Preserve the endpoint's error contract by catching this direct all() failure and throwing/returning the same 数据库查询失败 Result shape.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Source/Process.ts, line 679:
<comment>A D1 failure now becomes the generic `服务器运行错误` response instead of the existing database-query failure Result. Preserve the endpoint's error contract by catching this direct `all()` failure and throwing/returning the same `数据库查询失败` Result shape.</comment>
<file context>
@@ -669,13 +669,38 @@ export class Process {
+ // offset is clamped here and the rows are simply discarded when the
+ // range check below rejects the page.
+ const Offset = Math.max(0, (Data["Page"] - 1) * 15);
+ const Rows: Array<Record<string, any>> = (await this.RawDatabase.prepare(
+ "WITH post AS (" +
+ " SELECT p.user_id AS post_user_id, p.problem_id AS problem_id, p.title AS title, " +
</file context>
|
🧙 Sourcery is reviewing your pull request! Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GetPostissued five awaited queries in series: the post row, aCOUNToverbbs_reply, the board name, the lock row, and finally the page of replies. On Workers each of those is a separate round trip to D1, so opening a discussion cost five times the network latency regardless of how little data came back.Change
They collapse into a single statement built from two CTEs —
post(the post row left-joined tobbs_boardandbbs_lock) andpage(the fifteen replies for the requested page) — cross-joined so every returned row carries the post metadata alongside one reply, with the total reply count as a scalar subquery.The page's
LIMIT/OFFSEThas to be bound beforePageCountis known, so the offset is clamped at zero and the rows are discarded if the range check then rejects the page. Responses are byte-for-byte unchanged, including the out-of-range and empty-discussion cases.Verification
Built a SQLite database from
migrations/and ran the query against it for four cases: page 1 of a locked 20-reply post (15 rows, correct lock and board fields), page 2 (5 rows, correct offset), a post with zero replies (1 row of nulls,reply_count0), and a nonexistent post (0 rows).Checked the plan against the production index set (
idx_bbs_reply ON bbs_reply(post_id)) — every access is a seek, no table scans, and the count is served from a covering index:Added 9
GetPosttests covering those cases plus bind arguments, the legacy-domain rewrite, offset clamping, and mention clearing. One asserts exactly onepreparecall, so a regression back to N+1 fails the suite.npm test: 57 pass, 0 fail.Follow-ups, not in this PR
USE TEMP B-TREEabove).CREATE INDEX idx_bbs_reply_post_time ON bbs_reply(post_id, reply_time)removes it — verified. Invisible on small posts, real on large ones.GetPostsrunsROW_NUMBER() OVER (PARTITION BY post_id ...)across all ofbbs_replyto find each post's last reply, which no index can help. That is a full scan and sort on every list page load, and is probably a larger cost than anything here.🤖 Generated with Claude Code
Summary by cubic
Fetch an entire discussion page in
GetPostwith oneD1query, replacing five sequential calls to cut round trips and speed up discussion loads. The response shape and edge-case behavior stay the same.postandpageCTEs; returns post metadata, board name, lock state, per-page replies, andreply_countin one round trip.LIMIT/OFFSETup front with a zero-clamped offset; computePageCountfromreply_countand reject out-of-range pages; empty discussions still returnPageCount: 0.Written for commit dc05c2a. Summary will update on new commits.
Summary by Sourcery
Fetch full discussion metadata and replies for GetPost using a single SQL query instead of multiple round trips while preserving existing response semantics.
Enhancements:
Tests: