perf(2meet-data-optimizer): drive flat-sorted searches from the flat table

- A filtered search ordered by a flat column is rewritten to SELECT STRAIGHT_JOIN … FROM flat INNER JOIN wp_posts, with posts.ID tie-breaks (ours and plugin-appended) read as the equal flat post_id, so MariaDB sorts one table instead of looking every post up and sorting in a temporary table
- Only when the routed conditions already require a flat row on the sort group, without the copy route or a selective post filter; filter tmdo_router_flat_first turns it off; split_the_query compares with the same rewrite
- Verify: tests/live/run.php router (13/13); integration 522; dev36 page SQL Q07 263→147 ms, Q15 250→142 ms; fe.sh hourly_rate__desc 850→367 ms, ids == native
- PR: PR-062c
This commit is contained in:
2026-09-26 21:05:11 +08:00
parent a7134bfb80
commit dc9a8e9886
3 changed files with 206 additions and 5 deletions
@@ -337,10 +337,14 @@ class TMDO_Post_Query_Router {
public function posts_request( ?string $request, \WP_Query $query ): string {
$request = (string) ( $request ?? '' );
$routed = ! empty( $query->get( self::QUERY_VAR ) ) || ! empty( $query->get( self::SORT_VAR ) );
if ( ! $routed || false === strpos( $request, 'SQL_CALC_FOUND_ROWS' ) ) {
if ( ! $routed ) {
return $request;
}
$counted = false !== strpos( $request, 'SQL_CALC_FOUND_ROWS' );
$request = self::routed_request( $request, $query );
if ( ! $counted ) {
return $request;
}
$request = self::without_found_rows( $request );
$matches = preg_replace( '/\s+LIMIT\s+\d+(\s*,\s*\d+)?\s*$/i', '', $request );
$query->set( self::COUNT_VAR, "SELECT COUNT(*) FROM ( {$matches} ) AS tmdo_found" );
return $request;
@@ -367,7 +371,7 @@ class TMDO_Post_Query_Router {
global $wpdb;
// Core's other conditions: full rows, and a LIMIT under 500 or an external object cache.
return "{$wpdb->posts}.*" === ( $clauses['fields'] ?? '' )
&& self::without_found_rows( (string) $old_request ) === $query->request
&& self::routed_request( (string) $old_request, $query ) === $query->request
&& ( ( '' !== ( $clauses['limits'] ?? '' ) && (int) $query->get( 'posts_per_page' ) < 500 ) || wp_using_ext_object_cache() );
}
@@ -569,6 +573,81 @@ class TMDO_Post_Query_Router {
);
}
/**
* What posts_request makes of a routed request.
*
* @param string $request SELECT statement.
* @param \WP_Query $query Current WP_Query.
* @return string
*/
private static function routed_request( string $request, \WP_Query $query ): string {
return self::flat_first( self::without_found_rows( $request ), $query );
}
/**
* Drive a filtered search ordered by a flat column from its flat table.
*
* MariaDB starts from wp_posts (the type / status index), looks every
* candidate up in the flat table and sorts in a temporary table. From the
* flat table, with the posts.ID tie-break read as the equal flat post_id,
* it sorts one table (dev36, 100k listings: meta_value_num 263 → 147 ms,
* HivePress sort + filter 300 → 36 ms). Only when the routed conditions
* already require a flat row (LEFT JOIN = INNER JOIN), all on the sort
* group, without a selective post filter; `tmdo_router_flat_first` can
* turn it off.
*
* @param string $request SELECT statement.
* @param \WP_Query $query Current WP_Query.
* @return string
*/
private static function flat_first( string $request, \WP_Query $query ): string {
$routed = $query->get( self::QUERY_VAR );
$sort = $query->get( self::SORT_VAR );
if ( empty( $routed ) || ! is_array( $routed ) || ! empty( $query->get( self::COPY_VAR ) ) ) {
return $request;
}
$post_type = (string) array_key_first( $routed );
$groups = array_unique( array_column( $routed[ $post_type ], 'group' ) );
$compares = array_diff( array_column( $routed[ $post_type ], 'compare' ), array( 'NOT EXISTS' ) );
if ( empty( $sort ) || 1 !== count( $groups ) || empty( $compares ) || $sort['group'] !== $groups[0] ) {
return $request;
}
foreach ( array( 'p', 'page_id', 'name', 'pagename', 'attachment_id', 'post__in', 'post_parent', 'post_parent__in', 'author', 'author__in', 'author_name' ) as $var ) {
if ( ! empty( $query->get( $var ) ) ) {
return $request;
}
}
if ( ! apply_filters( 'tmdo_router_flat_first', true, $query ) ) {
return $request;
}
global $wpdb;
$table = $wpdb->prefix . 'wpdo_post_' . sanitize_key( $groups[0] );
$alias = self::table_alias( $post_type, $groups[0] );
$on = "(`{$wpdb->posts}`.`ID` = `{$alias}`.`post_id`)";
$join = " LEFT JOIN `{$table}` AS `{$alias}` ON {$on}";
if ( 1 !== substr_count( $request, $join ) ) {
return $request;
}
$flat_first = preg_replace(
'/\bFROM\s+' . preg_quote( $wpdb->posts, '/' ) . '\b/',
"FROM `{$table}` AS `{$alias}` INNER JOIN {$wpdb->posts} ON {$on}",
str_replace( $join, '', $request ),
1,
$replaced
);
if ( 1 !== $replaced ) {
return $request;
}
// Tie-breaks on posts.ID (ours, and any a plugin appended) read the flat post_id.
$flat_first = preg_replace_callback(
'/\bORDER BY\b(?!.*\bORDER BY\b).*$/s',
static fn( $m ) => preg_replace( '/`?' . preg_quote( $wpdb->posts, '/' ) . '`?\.`?ID`?(?!\w)/', "`{$alias}`.`post_id`", $m[0] ),
(string) $flat_first
);
return (string) preg_replace( '/^\s*SELECT\s/', 'SELECT STRAIGHT_JOIN ', (string) $flat_first, 1 );
}
/**
* A request without its SQL_CALC_FOUND_ROWS.
*
@@ -75,12 +75,12 @@ class PostQueryRouterFoundRowsTest extends TestCase {
}
/** A routed query plus the SQL WordPress would build for it (page size 1). */
private function routed(): array {
private function routed( array $clause = array( 'key' => 'fr_price', 'value' => 15, 'compare' => '>=', 'type' => 'NUMERIC' ) ): array {
global $wpdb;
$query = new WP_Query();
$query->set( 'post_type', 'fr_item' );
$query->set( 'post_status', 'publish' );
$query->set( 'meta_query', array( array( 'key' => 'fr_price', 'value' => 15, 'compare' => '>=', 'type' => 'NUMERIC' ) ) );
$query->set( 'meta_query', array( $clause ) );
$router = new TMDO_Post_Query_Router();
$router->pre_get_posts( $query );
@@ -134,6 +134,86 @@ class PostQueryRouterFoundRowsTest extends TestCase {
$this->assertNotSame( $after_write, wp_cache_get_last_changed( 'posts' ) );
}
// ── flat-first: a filtered search ordered by a flat column ─────────────
/**
* A routed search ordered by the flat price (named clause), with the
* posts.ID tie-break a plugin such as HivePress appends.
*
* @return array{0:TMDO_Post_Query_Router, 1:WP_Query, 2:string}
*/
private function sorted( string $compare = '>=' ): array {
global $wpdb;
$query = new WP_Query();
$query->set( 'post_type', 'fr_item' );
$query->set( 'post_status', 'publish' );
$query->set( 'meta_query', array( 'price' => array( 'key' => 'fr_price', 'value' => 15, 'compare' => $compare, 'type' => 'NUMERIC' ) ) );
$query->set( 'orderby', 'price' );
$query->set( 'order', 'DESC' );
$router = new TMDO_Post_Query_Router();
$router->pre_get_posts( $query );
$request = "SELECT SQL_CALC_FOUND_ROWS {$wpdb->posts}.ID FROM {$wpdb->posts} " . $router->posts_join( '', $query )
. " WHERE 1=1 AND {$wpdb->posts}.post_type = 'fr_item' AND (({$wpdb->posts}.post_status = 'publish'))" . $router->posts_where( '', $query )
. ' ORDER BY ' . $router->posts_orderby( '', $query ) . ", {$wpdb->posts}.ID DESC LIMIT 0, 10";
return array( $router, $query, $request );
}
public function test_sorted_filtered_request_drives_from_the_flat_table_with_the_same_rows(): void {
global $wpdb;
list( $router, $query, $request ) = $this->sorted();
$sql = $router->posts_request( $request, $query );
$this->assertStringStartsWith( 'SELECT STRAIGHT_JOIN ', $sql );
$this->assertStringContainsString( 'FROM `' . self::TABLE . '` AS `wpdoflat_fr_item_fr_core` INNER JOIN ' . $wpdb->posts . ' ON', $sql );
$this->assertStringNotContainsString( 'LEFT JOIN `' . self::TABLE . '`', $sql );
$this->assertStringEndsWith( 'ORDER BY `wpdoflat_fr_item_fr_core`.`fr_price` DESC, `wpdoflat_fr_item_fr_core`.`post_id` DESC, `wpdoflat_fr_item_fr_core`.`post_id` DESC LIMIT 0, 10', $sql, 'posts.ID tie-breaks read the flat post_id' );
$this->assertSame( array( '3', '2' ), $wpdb->get_col( $sql ) );
$this->assertSame( $wpdb->get_col( str_replace( 'SQL_CALC_FOUND_ROWS ', '', $request ) ), $wpdb->get_col( $sql ) );
}
public function test_date_ordered_filtered_request_keeps_wordpress_join_order(): void {
list( $router, $query, $request ) = $this->routed();
$this->assertStringStartsNotWith( 'SELECT STRAIGHT_JOIN', $router->posts_request( $request, $query ) );
}
public function test_flat_first_needs_a_condition_that_rejects_posts_without_a_flat_row(): void {
list( $router, $query, $request ) = $this->sorted( 'NOT EXISTS' );
$this->assertStringStartsNotWith( 'SELECT STRAIGHT_JOIN', $router->posts_request( $request, $query ) );
}
public function test_flat_first_is_skipped_for_selective_post_filters(): void {
list( $router, $query, $request ) = $this->sorted();
$query->set( 'post__in', array( 2 ) );
$this->assertStringStartsNotWith( 'SELECT STRAIGHT_JOIN', $router->posts_request( $request, $query ) );
}
public function test_flat_first_can_be_turned_off(): void {
list( $router, $query, $request ) = $this->sorted();
$off = static fn() => false;
add_filter( 'tmdo_router_flat_first', $off );
try {
$this->assertStringStartsNotWith( 'SELECT STRAIGHT_JOIN', $router->posts_request( $request, $query ) );
} finally {
remove_filter( 'tmdo_router_flat_first', $off );
}
}
public function test_flat_first_keeps_the_split_query(): void {
global $wpdb;
list( $router, $query, $request ) = $this->sorted();
$old_request = str_replace( "{$wpdb->posts}.ID FROM", "{$wpdb->posts}.* FROM", $request );
$query->request = $router->posts_request( $old_request, $query );
$query->set( 'posts_per_page', 10 );
$this->assertStringStartsWith( 'SELECT STRAIGHT_JOIN ', $query->request );
$this->assertTrue( $router->split_the_query( false, $query, $old_request, array( 'fields' => "{$wpdb->posts}.*", 'limits' => 'LIMIT 0, 10' ) ) );
}
// ── split query: IDs first, then cached post objects ──────────────────
/**
+42
View File
@@ -381,3 +381,45 @@ TMDO_Live::test(
tmdo_live_assert_true( 1 === preg_match( '/^\s*SELECT\s+' . preg_quote( $wpdb->posts, '/' ) . '\.ID\s/', $routed->request ), 'IDs first: ' . $routed->request );
}
);
TMDO_Live::test(
'a filtered search ordered by a flat column drives from the flat table and matches native',
static function () {
foreach ( array( 'hp_price', 'hp_hourly_rate' ) as $key ) {
TMDO_Live_Fixtures::field( 'post', $key, 'hp_listing' );
}
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
// A price band no seeded listing uses keeps the result to the fixtures.
tmdo_live_router_posts(
'hp_listing',
array(
'A' => array( 'hp_price' => '9876501', 'hp_hourly_rate' => '12' ),
'B' => array( 'hp_price' => '9876502', 'hp_hourly_rate' => '15' ),
'C' => array( 'hp_price' => '9876503' ),
)
);
$args = array(
'post_type' => 'hp_listing',
'post_status' => 'any',
'posts_per_page' => 10,
'fields' => 'ids',
'suppress_filters' => false,
'meta_query' => array( array( 'key' => 'hp_price', 'value' => array( 9876500, 9876510 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ),
'meta_key' => 'hp_price',
'orderby' => 'meta_value_num',
'order' => 'DESC',
);
$native = new WP_Query( $args );
$router = new TMDO_Post_Query_Router();
$router->register_hooks();
try {
$routed = new WP_Query( $args );
} finally {
tmdo_live_router_unhook( $router );
}
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), 'ids' );
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), 'flat-first: ' . $routed->request );
}
);