fix: order and bound the revisions query in WikiPage.get_context - #742
fix: order and bound the revisions query in WikiPage.get_context#742Aanzan426 wants to merge 2 commits into
Conversation
`get_context` loads every revision of a page, with full `content` on each, then uses only `revisions[0]` and `revisions[1]`. Correctness first: there is no `ORDER BY`, so `revisions[0]` is whatever the database happens to return first. It is not guaranteed to be the newest revision, which is what `context.current_revision` is used as. Today it is usually right by insertion order; that is luck, not a guarantee, and it can change with storage engine, replication or a table rebuild. Adding `order_by="creation desc"` makes it correct, and once the order is defined, `limit_page_length=2` makes it cheap. Measured on a page with 107 revisions: 32.7 ms to 5.7 ms, and 3.37 MB of revision content loaded to use 130 KB of it. The saving scales with edit history, so the pages that cost the most are the most-edited ones. No behaviour change for callers: the two values assigned are the same two, and the `len(revisions) > 1` branch still distinguishes a page with one revision.
sankarsubramaniankvs
left a comment
There was a problem hiding this comment.
Code Review - PR #742
Reviewer: OpenClaw Bot
Verdict: Request Changes
Files reviewed: 1 | Issues found: Critical 0 Major 1 Minor 0 Suggestions 0
Summary
The code change is directionally right: current_revision should not depend on unordered database results, and bounding the query to the two rows the template actually uses is a clean performance win. The issue is that this changes backend behavior and currently ships without a regression test, while the repo already has wiki/wiki/doctype/wiki_page/test_wiki_page.py covering the Wiki Page lifecycle.
What's Done Well
order_by="creation desc"fixes the correctness problem directly.limit_page_length=2is the right bound because only current and previous revision are used.- The change is small and localized.
Issues
Major
wiki/wiki/doctype/wiki_page/wiki_page.py:304 - Missing regression test for revision ordering
This fixes a real backend correctness issue, but there is no test proving get_context() now chooses the newest revision as current_revision and the next newest as previous_revision. Without that coverage, a future refactor can drop the order_by or limit and silently reintroduce the same unstable behavior.
Please add a small test under wiki/wiki/doctype/wiki_page/test_wiki_page.py that creates or updates a page to produce at least three revisions, calls get_context(), and asserts:
context.current_revisionis the newest revisioncontext.previous_revisionis the second-newest revision- older revisions are not selected
If you want to make the performance part explicit, monkeypatching/wrapping frappe.db.get_all to assert order_by="creation desc" and limit_page_length=2 is also fine, but a behavior test is the important bit.
File-by-File Summary
wiki/wiki/doctype/wiki_page/wiki_page.py- Correct query change, needs test coverage.
Checks reviewed:
- GitHub reports no checks on
fix/bound-revisions-query.
Reviewed by OpenClaw Bot - Unity Edu
Two tests in `test_wiki_page.py`: - `test_get_context_orders_revisions_by_creation_not_modified` gives three revisions a `modified` order opposite to their `creation` order, so the doctype's default `modified DESC` sort and chronological order disagree, then asserts `current_revision` and `previous_revision` are the two newest *by creation*. Verified to fail without the fix and pass with it. - `test_get_context_handles_a_page_with_one_revision` pins the single-revision case, where `previous_revision` must remain the "No Revisions" placeholder -- the branch most easily broken by adding a limit. Note for reviewers: my original description claimed the query had no `ORDER BY`. That was wrong. Frappe injects one from the doctype's `sort_field`, so the unpatched query emits `ORDER BY \`tabWiki Page Revision\`.\`modified\` DESC`. The ordering is therefore deterministic today, just sorted by the wrong column -- `modified` is when a row was last written, not when the revision was made, so touching an old revision presents it as the current one. The missing `LIMIT` is unaffected by that correction and remains the larger practical win.
|
Tests added in CorrectionMy original description said the query had no Frappe supplies a default So the ordering is deterministic today — it is just sorted by the wrong column. I caught this because the first version of the test passed without the fix applied. I had set What this means for the change
Tests
Full Happy to drop the |
1 similar comment
|
Tests added in CorrectionMy original description said the query had no Frappe supplies a default So the ordering is deterministic today — it is just sorted by the wrong column. I caught this because the first version of the test passed without the fix applied. I had set What this means for the change
Tests
Full Happy to drop the |
sankarsubramaniankvs
left a comment
There was a problem hiding this comment.
Code Review - PR #742
Reviewer: OpenClaw Bot
Verdict: Approve
Files reviewed: 2 | Issues found: Critical 0 Major 0 Minor 0 Suggestions 0
Summary
The earlier blocker is addressed. The implementation now explicitly selects the two newest revisions by creation desc, which fixes the correctness issue around modified ordering and avoids loading the full revision history when only current/previous are used.
What's Done Well
order_by="creation desc"captures the actual revision chronology instead of relying on the DocType defaultmodified descsort.limit_page_length=2is the right bound for this code path because onlycurrent_revisionandprevious_revisionare consumed.- The added tests cover both the ordering regression and the single-revision placeholder branch, including the case where
modifiedorder disagrees withcreationorder.
File-by-File Summary
wiki/wiki/doctype/wiki_page/wiki_page.py- Clean, localized query fix.wiki/wiki/doctype/wiki_page/test_wiki_page.py- Good regression coverage for the fixed behavior.
Checks reviewed:
- GitHub reports no checks on
fix/bound-revisions-query. - I did not run the Frappe test suite locally in this workspace.
Reviewed by OpenClaw Bot - Unity Edu
The problem
WikiPage.get_contextloads every revision of a page, with the fullcontentblob on each, and then uses onlyrevisions[0]andrevisions[1]:Two things are wrong with it, one large and one small.
1. There is no
LIMIT. Only two rows are ever used, but every revision is fetched with its fullcontent. On a page with 107 revisions that is 3.37 MB read to use 130 KB, and it grows with edit history — so the most-edited pages, usually also the most-read, pay the most.2. The ordering is by the wrong column. There is an implicit
ORDER BY: Frappe supplies one from the doctype'ssort_field, so the query emitsmodifiedis when the row was last written, not when the revision was made. They coincide while revisions are only ever appended, but any write that touches an older revision's row moves it to the front, and it is then handed to the template ascurrent_revision. Sorting explicitly bycreationsays what is actually meant.The fix
Impact
Measured on a page with 107 revisions:
On a page with a single revision the difference is negligible either way.
Tests
Two tests added to
wiki/wiki/doctype/wiki_page/test_wiki_page.py:test_get_context_orders_revisions_by_creation_not_modified— gives three revisions amodifiedorder deliberately opposite to theircreationorder, so the defaultmodified DESCsort and chronological order disagree, then assertscurrent_revisionandprevious_revisionare the two newest by creation. Verified to fail onmasterand pass with this change.test_get_context_handles_a_page_with_one_revision— pins the single-revision case, whereprevious_revisionmust stay the"No Revisions"placeholder. That is the branch most easily broken by adding a limit.Compatibility
No behaviour change for callers. The same two values are assigned, and
len(revisions) > 1still distinguishes a one-revision page: the query returns one row, so the placeholder path is preserved.Found while profiling wiki page rendering under concurrent load.