Skip to content

fix: order and bound the revisions query in WikiPage.get_context - #1

Open
Aanzan426 wants to merge 2 commits into
masterfrom
fix/bound-revisions-query
Open

fix: order and bound the revisions query in WikiPage.get_context#1
Aanzan426 wants to merge 2 commits into
masterfrom
fix/bound-revisions-query

Conversation

@Aanzan426

Copy link
Copy Markdown

Bounds and orders the revisions query in WikiPage.get_context. This is the Unity-owned copy of the change — mergeable by us, on the branch we actually deploy (master, 2372f859f6).

Also raised upstream as frappe/wiki#742 so the fix is on record with the maintainers. That one is not mergeable by us and frappe/wiki:master has had no commits since 2025-12-18, so it should be treated as a contribution, not as our delivery path.

The problem

get_context loads every revision of a page, with the full content blob on each, then uses only revisions[0] and revisions[1]:

revisions = frappe.db.get_all(
    "Wiki Page Revision",
    filters=[["wiki_page", "=", self.name]],
    fields=["content", "creation", "owner", "name", "raised_by", "raised_by_username"],
)
context.current_revision = revisions[0]
if len(revisions) > 1:
    context.previous_revision = revisions[1]

Two problems, one large and one small.

1. There is no LIMIT. Only two rows are ever used. 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's sort_field, so the query emits:

ORDER BY `tabWiki Page Revision`.`modified` DESC

modified is when the row was last written, not when the revision was made. They coincide while revisions are only appended, but any write touching an older revision's row moves it to the front, and it is then handed to the template as current_revision.

The fix

  fields=["content", "creation", "owner", "name", "raised_by", "raised_by_username"],
+ order_by="creation desc",
+ limit_page_length=2,

Impact

Measured on a page with 107 revisions:

before after
query time 32.7 ms 5.7 ms
revision content read 3.37 MB 130 KB

On a single-revision page the difference is negligible either way — this is not a median-case win, it is an outlier and correctness win.

Tests

  • test_get_context_orders_revisions_by_creation_not_modified — gives three revisions a modified order deliberately opposite to their creation order, so the default sort and chronological order disagree, then asserts current_revision / previous_revision are the two newest by creation. Verified to fail on master and pass with this change.
  • test_get_context_handles_a_page_with_one_revision — pins the single-revision case, where previous_revision must stay the "No Revisions" placeholder. That is the branch a LIMIT is most likely to break.

Full test_wiki_page.py suite: 4 tests, all passing.

Compatibility

No behaviour change for callers. The same two values are assigned, and len(revisions) > 1 still distinguishes a one-revision page: the query returns one row, so the placeholder path is preserved.

Deployment note

Merging this puts the fix in the Unity fork. It only reaches walnut if the bench is pointed at the fork for wiki — a separate decision, and one that cuts against the overrides-first approach used for walnut_customizations#114. Merging here is still worth it: it means the fix exists somewhere we control rather than only in an upstream PR that may sit indefinitely.

Ref: WalnutAppSuite/walnut_customizations#114

`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.
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.
@sankarsubramaniankvs

Copy link
Copy Markdown

Code Review - PR #1

Reviewer: OpenClaw Bot
Verdict: Approve
Files reviewed: 2 | Issues found: Critical 0, Major 0, Minor 0, Suggestion 0

Summary

This is a small, targeted fix to WikiPage.get_context: it makes revision selection deterministic by creation time and avoids loading every revision when only the current and previous revisions are used. The tests cover both the corrected ordering behavior and the single-revision placeholder path.

What's Done Well

  • The production change is minimal: explicit order_by="creation desc" plus limit_page_length=2.
  • The ordering test deliberately makes modified DESC disagree with creation DESC, which proves the actual bug rather than only testing the happy path.
  • The single-revision test protects the branch most likely to regress from adding the limit.
  • The PR description clearly explains the fork/upstream split and why this Unity-owned PR is the deployable path.

Issues

No blocking issues found.

File-by-File Summary

  • wiki/wiki/doctype/wiki_page/wiki_page.py - Clean. Query now selects the two intended rows deterministically and cheaply.
  • wiki/wiki/doctype/wiki_page/test_wiki_page.py - Clean. Coverage is focused and relevant.

Note: GitHub reports no checks on this branch, so I reviewed the code and test intent but did not see CI results here.

Reviewed by OpenClaw Bot - Unity Edu

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.

2 participants