Conversation
…a range reads one index range Each condition on a field joined its table again, i.e. date>=a, date<b joined field_date twice, and num>=a, num<b checked the < in a LEFT JOIN that allows for no value (counted as 0). MySQL/MariaDB can't combine conditions on different aliases into one index range, so they read every page: about 30 ms for a range of 171 pages among 20k. When a table has one row per page (a primary key of just pages_id), its inner joins all reach the same row, so PageFinder2 merges them into one, and replaces a LEFT JOIN for no value with the inner join when there is one. The results are the same; the range now takes about 2 ms.
adrianbj
force-pushed
the
feature/pagefinder-range-join
branch
from
September 29, 2026 16:10
670ea51 to
96704fa
Compare
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.
Moved to PageFinder2, as Ryan asked; PageFinder is unchanged. This is on top of current dev.
Each condition on a field joins its table again.
date>=2024-01-01, date<2025-01-01joinsfield_datetwice.num>=40000, num<41000checks the<in a LEFT JOIN, because<also matches pages with no value (counted as 0). MySQL and MariaDB can't combine conditions on different aliases into one index range. So when neither bound is selective on its own, they read every page and look up both joins for each one.When a field's table has one row per page (a primary key of just
pages_id), every join of it reaches the same row. So in PageFinder2:mergeSingleRowJoins()merges the inner joins of such a table into one, with all their conditions, and renames the merged aliases wherever the query uses them.mergeBlankJoins()handles the LEFT JOIN thatwhereEmptyValuePossible()adds for "no value". When the same table is also inner joined, that LEFT JOIN is dropped and its conditions use the inner join: the page must have the row anyway, and it's the same row.Both rewrites give the same results. Fields with several rows per page, such as
FieldtypeMulti, keep their joins as before, as do OR-groups and!=.$blankJoinsis reset at the start of eachgetQuery(), and PageFinder2's subqueries use other instances, so reused instances don't mix them up.Numbers
PageFinder2 on MariaDB 13.0.2 with 20k pages. Each row compares against a PageFinder2 subclass with both merges turned off, in the same process, alternating, as the median of 10 batches:
num>=40000, num<41000(171 pages)sort=-numnum>40000, num<=40500title%=asort=title, limit=10num>=40000, num>=40500(two joins, as for a date range)body~=mine, body~=kalonum<500,num<500, num!=100, OR-groupsEvery selector returned the same pages both ways. The only difference was the order of pages with equal values under
sort=-num, which the query doesn't specify.Tests
PageFinder2.test.php(new, with its own fixtures) getstestRangeOnSingleValueField(). It uses a temporaryFieldtypeIntegerfield, whichfinish()removes. It checks:<=/>, and two lower bounds: the right pages, with one join and no LEFT JOIN;<still matches a page with no value, and still checks for one;<with!=, and OR-groups, give the same pages as before.WireTests: SQLite and PostgreSQL 111/113 each (MarkupRSS's fresh-install check and WireHttp), MariaDB 111/115 (the same four environmental failures as before).
#377 also adds a
PageFinder2.test.php, with the same fixtures. Whichever of the two merges second, I'll rebase it and combine the files.-Adrian (via Claude Code)
🤖 Generated with Claude Code