From 7851f150faea12989020e3fa6aaa16629fbb016b Mon Sep 17 00:00:00 2001 From: wpdev Date: Sun, 27 Sep 2026 17:39:36 +0800 Subject: [PATCH] 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 "%" ({}), 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) --- .../query/class-tmdo-post-query-router.php | 7 ++- .../PostQueryRouterFoundRowsTest.php | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/includes/query/class-tmdo-post-query-router.php b/includes/query/class-tmdo-post-query-router.php index acdb148..b05e5cb 100644 --- a/includes/query/class-tmdo-post-query-router.php +++ b/includes/query/class-tmdo-post-query-router.php @@ -433,8 +433,11 @@ class TMDO_Post_Query_Router { global $wpdb; list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' ); - $key = md5( $count_sql ) . ':' . wp_cache_get_last_changed( 'posts' ); - $total = wp_cache_get( $key, self::COUNT_CACHE_GROUP ); + // Keyword searches carry wpdb's placeholder escape for "%", which is new + // 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 ) { $total = (int) $wpdb->get_var( $count_sql ); wp_cache_set( $key, $total, self::COUNT_CACHE_GROUP, HOUR_IN_SECONDS ); diff --git a/tests/integration/PostQueryRouterFoundRowsTest.php b/tests/integration/PostQueryRouterFoundRowsTest.php index a374e91..c593b65 100644 --- a/tests/integration/PostQueryRouterFoundRowsTest.php +++ b/tests/integration/PostQueryRouterFoundRowsTest.php @@ -157,6 +157,49 @@ class PostQueryRouterFoundRowsTest extends TestCase { $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 { $GLOBALS['_wp_current_filter'] = 'update_post_metadata'; $before = wp_cache_get_last_changed( 'posts' );