perf(2meet-data-optimizer): key the routed count cache on the SQL without wpdb's placeholder escape

- keyword searches carry wpdb's placeholder escape for "%" ({<hash>}), which
  is new in every request, so md5(count SQL) never matched across requests
  and every keyword search recounted (the 2.2 "keyword + filter routed slower
  than native" finding). The key now uses remove_placeholder_escape()
- found in the 2.3 acceptance: dev36 keyword + price routed 6.2 s vs native
  4.9 s; PR-091's in-process check reused one process, one escape
- Verify: PostQueryRouterFoundRowsTest (two escapes, one COUNT), integration
  588, unit 703, phpcs, stan; dev36 front-end
- PR: PR-116 (2.3 acceptance)
This commit is contained in:
2026-09-27 17:39:36 +08:00
parent 1835c8776a
commit 7851f150fa
2 changed files with 48 additions and 2 deletions
@@ -433,8 +433,11 @@ class TMDO_Post_Query_Router {
global $wpdb; global $wpdb;
list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' ); list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' );
$key = md5( $count_sql ) . ':' . wp_cache_get_last_changed( 'posts' ); // Keyword searches carry wpdb's placeholder escape for "%", which is new
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP ); // in every request: without it the key would never match again.
$stable = method_exists( $wpdb, 'remove_placeholder_escape' ) ? $wpdb->remove_placeholder_escape( $count_sql ) : $count_sql;
$key = md5( $stable ) . ':' . wp_cache_get_last_changed( 'posts' );
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
if ( false === $total ) { if ( false === $total ) {
$total = (int) $wpdb->get_var( $count_sql ); $total = (int) $wpdb->get_var( $count_sql );
wp_cache_set( $key, $total, self::COUNT_CACHE_GROUP, HOUR_IN_SECONDS ); wp_cache_set( $key, $total, self::COUNT_CACHE_GROUP, HOUR_IN_SECONDS );
@@ -157,6 +157,49 @@ class PostQueryRouterFoundRowsTest extends TestCase {
$this->assertSame( '0', $count( '2' ), 'recounted' ); $this->assertSame( '0', $count( '2' ), 'recounted' );
} }
/**
* Keyword searches carry wpdb's placeholder escape for "%", which changes
* with every request: the cache key uses the SQL without it, or no search
* with a keyword ever hits the cached count.
*/
public function test_the_cached_count_survives_a_new_placeholder_escape(): void {
global $wpdb;
$real = $wpdb;
$wpdb = new class( $real ) {
public int $counts = 0;
public function __construct( private object $db ) {}
public function remove_placeholder_escape( string $sql ): string {
return (string) preg_replace( '/\{[0-9a-f]{8}\}/', '%', $sql );
}
public function __call( string $name, array $args ) {
if ( 'get_var' === $name && str_contains( (string) $args[0], 'COUNT(*)' ) ) {
++$this->counts;
}
return $this->db->$name( ...$args );
}
public function __get( string $name ) {
return $this->db->$name;
}
};
try {
foreach ( array( '{1a2b3c4d}', '{9f8e7d6c}' ) as $escape ) { // Two requests.
list( $router, $query, $request ) = $this->routed();
$request = str_replace( ' GROUP BY', " AND '{$escape}' = '{$escape}' GROUP BY", $request );
$router->posts_request( $request, $query );
$this->assertSame( '2', $real->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ) );
}
$counts = $wpdb->counts;
} finally {
$wpdb = $real;
}
$this->assertSame( 1, $counts, 'counted once' );
}
public function test_flat_writes_and_deletes_move_posts_last_changed(): void { public function test_flat_writes_and_deletes_move_posts_last_changed(): void {
$GLOBALS['_wp_current_filter'] = 'update_post_metadata'; $GLOBALS['_wp_current_filter'] = 'update_post_metadata';
$before = wp_cache_get_last_changed( 'posts' ); $before = wp_cache_get_last_changed( 'posts' );