From 449bb38edd55e904977a1dffba81e0e7be7835cc Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Mon, 6 Apr 2026 15:51:17 +0530 Subject: [PATCH 1/3] Cache: Introduce meta-specific last_changed keys to reduce cache over-invalidation --- src/wp-includes/class-wp-comment-query.php | 5 +++- src/wp-includes/class-wp-query.php | 3 +++ src/wp-includes/class-wp-site-query.php | 5 +++- src/wp-includes/class-wp-term-query.php | 5 +++- src/wp-includes/class-wp-user-query.php | 4 ++++ src/wp-includes/comment.php | 12 ++++++++++ src/wp-includes/default-filters.php | 24 +++++++++---------- src/wp-includes/ms-default-filters.php | 6 ++--- src/wp-includes/ms-site.php | 12 ++++++++++ src/wp-includes/post.php | 12 ++++++++++ src/wp-includes/taxonomy.php | 12 ++++++++++ src/wp-includes/user.php | 12 ++++++++++ tests/phpunit/tests/cache/metaLastChanged.php | 13 ++++++++++ 13 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 tests/phpunit/tests/cache/metaLastChanged.php diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index cfabfd7e6b964..3733e970561d1 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -449,7 +449,10 @@ public function get_comments() { unset( $_args['fields'], $_args['update_comment_meta_cache'], $_args['update_comment_post_cache'] ); $key = md5( serialize( $_args ) ); - $last_changed = wp_cache_get_last_changed( 'comment' ); + $last_changed = (array) wp_cache_get_last_changed( 'comment' ); + if ( ! empty( $this->meta_query->queries ) ) { + $last_changed[] = wp_cache_get_last_changed( 'comment-meta' ); + } $cache_key = "get_comments:$key"; $cache_value = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed ); diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index cf07b07d977c3..3c2877c1bfa5d 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -3249,6 +3249,9 @@ public function get_posts() { if ( ! empty( $this->tax_query->queries ) ) { $last_changed[] = wp_cache_get_last_changed( 'terms' ); } + if ( ! empty( $this->meta_query->queries ) ) { + $last_changed[] = wp_cache_get_last_changed( 'posts-meta' ); + } if ( $query_vars['cache_results'] && $id_query_is_cacheable ) { $new_request = str_replace( $fields, "{$wpdb->posts}.*", $this->request ); diff --git a/src/wp-includes/class-wp-site-query.php b/src/wp-includes/class-wp-site-query.php index 52ae228d90af0..4a8749a9b3ddc 100644 --- a/src/wp-includes/class-wp-site-query.php +++ b/src/wp-includes/class-wp-site-query.php @@ -355,7 +355,10 @@ public function get_sites() { unset( $_args['fields'], $_args['update_site_cache'], $_args['update_site_meta_cache'] ); $key = md5( serialize( $_args ) ); - $last_changed = wp_cache_get_last_changed( 'sites' ); + $last_changed = (array) wp_cache_get_last_changed( 'sites' ); + if ( ! empty( $this->meta_query->queries ) ) { + $last_changed[] = wp_cache_get_last_changed( 'sites-meta' ); + } $cache_key = "get_sites:$key"; $cache_value = wp_cache_get_salted( $cache_key, 'site-queries', $last_changed ); diff --git a/src/wp-includes/class-wp-term-query.php b/src/wp-includes/class-wp-term-query.php index a30d887aa56d1..51fe6389fd5ef 100644 --- a/src/wp-includes/class-wp-term-query.php +++ b/src/wp-includes/class-wp-term-query.php @@ -775,7 +775,10 @@ public function get_terms() { if ( $args['cache_results'] ) { $cache_key = $this->generate_cache_key( $args, $this->request ); - $last_changed = wp_cache_get_last_changed( 'terms' ); + $last_changed = (array) wp_cache_get_last_changed( 'terms' ); + if ( ! empty( $this->meta_query->queries ) ) { + $last_changed[] = wp_cache_get_last_changed( 'terms-meta' ); + } $cache = wp_cache_get_salted( $cache_key, 'term-queries', $last_changed ); if ( false !== $cache ) { diff --git a/src/wp-includes/class-wp-user-query.php b/src/wp-includes/class-wp-user-query.php index 3815023924489..0dc82a7229408 100644 --- a/src/wp-includes/class-wp-user-query.php +++ b/src/wp-includes/class-wp-user-query.php @@ -1071,6 +1071,10 @@ protected function generate_cache_key( array $deprecated, $sql ) { protected function get_cache_last_changed( array $args ) { $last_changed = (array) wp_cache_get_last_changed( 'users' ); + if ( ! empty( $this->meta_query->queries ) ) { + $last_changed[] = wp_cache_get_last_changed( 'users-meta' ); + } + if ( empty( $args['orderby'] ) ) { // Default order is by 'user_login'. $ordersby = array( 'user_login' => '' ); diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 5395997ecd0ef..12cba07271f18 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -4071,6 +4071,18 @@ function wp_cache_set_comments_last_changed() { wp_cache_set_last_changed( 'comment' ); } +/** + * Sets the last changed time for the 'comment-meta' cache group. + * + * This is used to invalidate comment query caches that include meta queries, + * without affecting query caches for queries that do not use meta. + * + * @since x.x.x + */ +function wp_cache_set_comments_meta_last_changed() { + wp_cache_set_last_changed( 'comment-meta' ); +} + /** * Updates the comment type for a batch of comments. * diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 4b6d9de25fa11..caf4fee295582 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -122,22 +122,22 @@ } // Post meta. -add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); -add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); -add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed' ); +add_action( 'added_post_meta', 'wp_cache_set_posts_meta_last_changed' ); +add_action( 'updated_post_meta', 'wp_cache_set_posts_meta_last_changed' ); +add_action( 'deleted_post_meta', 'wp_cache_set_posts_meta_last_changed' ); // User meta. -add_action( 'added_user_meta', 'wp_cache_set_users_last_changed' ); -add_action( 'updated_user_meta', 'wp_cache_set_users_last_changed' ); -add_action( 'deleted_user_meta', 'wp_cache_set_users_last_changed' ); +add_action( 'added_user_meta', 'wp_cache_set_users_meta_last_changed' ); +add_action( 'updated_user_meta', 'wp_cache_set_users_meta_last_changed' ); +add_action( 'deleted_user_meta', 'wp_cache_set_users_meta_last_changed' ); add_action( 'add_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'set_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'remove_user_role', 'wp_cache_set_users_last_changed' ); // Term meta. -add_action( 'added_term_meta', 'wp_cache_set_terms_last_changed' ); -add_action( 'updated_term_meta', 'wp_cache_set_terms_last_changed' ); -add_action( 'deleted_term_meta', 'wp_cache_set_terms_last_changed' ); +add_action( 'added_term_meta', 'wp_cache_set_terms_meta_last_changed' ); +add_action( 'updated_term_meta', 'wp_cache_set_terms_meta_last_changed' ); +add_action( 'deleted_term_meta', 'wp_cache_set_terms_meta_last_changed' ); add_filter( 'get_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'add_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata', 'wp_check_term_meta_support_prefilter' ); @@ -148,9 +148,9 @@ add_filter( 'update_term_metadata_cache', 'wp_check_term_meta_support_prefilter' ); // Comment meta. -add_action( 'added_comment_meta', 'wp_cache_set_comments_last_changed' ); -add_action( 'updated_comment_meta', 'wp_cache_set_comments_last_changed' ); -add_action( 'deleted_comment_meta', 'wp_cache_set_comments_last_changed' ); +add_action( 'added_comment_meta', 'wp_cache_set_comments_meta_last_changed' ); +add_action( 'updated_comment_meta', 'wp_cache_set_comments_meta_last_changed' ); +add_action( 'deleted_comment_meta', 'wp_cache_set_comments_meta_last_changed' ); add_action( 'init', 'wp_create_initial_comment_meta' ); // Places to balance tags on input. diff --git a/src/wp-includes/ms-default-filters.php b/src/wp-includes/ms-default-filters.php index 8682d48e18e45..2e376aff37e0b 100644 --- a/src/wp-includes/ms-default-filters.php +++ b/src/wp-includes/ms-default-filters.php @@ -55,9 +55,9 @@ add_action( 'update_blog_public', 'wp_update_blog_public_option_on_site_update', 1, 2 ); // Site meta. -add_action( 'added_blog_meta', 'wp_cache_set_sites_last_changed' ); -add_action( 'updated_blog_meta', 'wp_cache_set_sites_last_changed' ); -add_action( 'deleted_blog_meta', 'wp_cache_set_sites_last_changed' ); +add_action( 'added_blog_meta', 'wp_cache_set_sites_meta_last_changed' ); +add_action( 'updated_blog_meta', 'wp_cache_set_sites_meta_last_changed' ); +add_action( 'deleted_blog_meta', 'wp_cache_set_sites_meta_last_changed' ); add_filter( 'get_blog_metadata', 'wp_check_site_meta_support_prefilter' ); add_filter( 'add_blog_metadata', 'wp_check_site_meta_support_prefilter' ); add_filter( 'update_blog_metadata', 'wp_check_site_meta_support_prefilter' ); diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 6399dab72e881..64638b587c9aa 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1320,6 +1320,18 @@ function wp_cache_set_sites_last_changed() { wp_cache_set_last_changed( 'sites' ); } +/** + * Sets the last changed time for the 'sites-meta' cache group. + * + * This is used to invalidate site query caches that include meta queries, + * without affecting query caches for queries that do not use meta. + * + * @since x.x.x + */ +function wp_cache_set_sites_meta_last_changed() { + wp_cache_set_last_changed( 'sites-meta' ); +} + /** * Aborts calls to site meta if it is not supported. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index b225d35c48b2a..1f216b0ae6f2f 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -8450,6 +8450,18 @@ function wp_cache_set_posts_last_changed() { wp_cache_set_last_changed( 'posts' ); } +/** + * Sets the last changed time for the 'posts-meta' cache group. + * + * This is used to invalidate post query caches that include meta queries, + * without affecting query caches for queries that do not use meta. + * + * @since x.x.x + */ +function wp_cache_set_posts_meta_last_changed() { + wp_cache_set_last_changed( 'posts-meta' ); +} + /** * Gets all available post MIME types for a given post type. * diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 80f457de0e6f7..1604a268cf684 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -5142,6 +5142,18 @@ function wp_cache_set_terms_last_changed() { wp_cache_set_last_changed( 'terms' ); } +/** + * Sets the last changed time for the 'terms-meta' cache group. + * + * This is used to invalidate term query caches that include meta queries, + * without affecting query caches for queries that do not use meta. + * + * @since x.x.x + */ +function wp_cache_set_terms_meta_last_changed() { + wp_cache_set_last_changed( 'terms-meta' ); +} + /** * Aborts calls to term meta if it is not supported. * diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 9c635f63d288a..db5f7ea5fa36e 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -5239,6 +5239,18 @@ function wp_cache_set_users_last_changed() { wp_cache_set_last_changed( 'users' ); } +/** + * Sets the last changed time for the 'users-meta' cache group. + * + * This is used to invalidate user query caches that include meta queries, + * without affecting query caches for queries that do not use meta. + * + * @since x.x.x + */ +function wp_cache_set_users_meta_last_changed() { + wp_cache_set_last_changed( 'users-meta' ); +} + /** * Checks if password reset is allowed for a specific user. * diff --git a/tests/phpunit/tests/cache/metaLastChanged.php b/tests/phpunit/tests/cache/metaLastChanged.php new file mode 100644 index 0000000000000..c8fcdc9c16936 --- /dev/null +++ b/tests/phpunit/tests/cache/metaLastChanged.php @@ -0,0 +1,13 @@ + Date: Mon, 6 Apr 2026 16:24:03 +0530 Subject: [PATCH 2/3] Tests: Add test coverage for meta-specific cache invalidation --- tests/phpunit/tests/cache/metaLastChanged.php | 384 +++++++++++++++++- 1 file changed, 383 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/cache/metaLastChanged.php b/tests/phpunit/tests/cache/metaLastChanged.php index c8fcdc9c16936..9eb5763fbb0b3 100644 --- a/tests/phpunit/tests/cache/metaLastChanged.php +++ b/tests/phpunit/tests/cache/metaLastChanged.php @@ -9,5 +9,387 @@ */ class Tests_Cache_MetaLastChanged extends WP_UnitTestCase { - + /** + * Post IDs. + * + * @var int[] + */ + protected static $post_ids; + + /** + * Term IDs. + * + * @var int[] + */ + protected static $term_ids; + + /** + * Comment IDs. + * + * @var int[] + */ + protected static $comment_ids; + + /** + * User IDs. + * + * @var int[] + */ + protected static $user_ids; + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$post_ids = $factory->post->create_many( 3 ); + + self::$term_ids = array( + $factory->term->create( array( 'taxonomy' => 'category' ) ), + $factory->term->create( array( 'taxonomy' => 'category' ) ), + ); + + self::$comment_ids = array( + $factory->comment->create( array( 'comment_post_ID' => self::$post_ids[0] ) ), + $factory->comment->create( array( 'comment_post_ID' => self::$post_ids[0] ) ), + ); + + self::$user_ids = $factory->user->create_many( + 2, + array( 'role' => 'author' ) + ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_posts_meta_last_changed + */ + public function test_wp_cache_set_posts_meta_last_changed_updates_posts_meta_group() { + wp_cache_set_posts_meta_last_changed(); + $last_changed = wp_cache_get_last_changed( 'posts-meta' ); + $this->assertNotEmpty( $last_changed, 'posts-meta last_changed should be set.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_posts_meta_last_changed + */ + public function test_adding_post_meta_updates_posts_meta_group_not_posts_group() { + $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); + + add_post_meta( self::$post_ids[0], 'test_key', 'test_value' ); + + $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); + + $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is added.' ); + $this->assertNotSame( $posts_meta_before, $posts_meta_after, 'posts-meta last_changed should change when post meta is added.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_posts_meta_last_changed + */ + public function test_updating_post_meta_updates_posts_meta_group_not_posts_group() { + add_post_meta( self::$post_ids[0], 'update_key', 'old_value' ); + + $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); + + update_post_meta( self::$post_ids[0], 'update_key', 'new_value' ); + + $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); + + $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is updated.' ); + $this->assertNotSame( $posts_meta_before, $posts_meta_after, 'posts-meta last_changed should change when post meta is updated.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_posts_meta_last_changed + */ + public function test_deleting_post_meta_updates_posts_meta_group_not_posts_group() { + add_post_meta( self::$post_ids[0], 'delete_key', 'value' ); + + $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); + + delete_post_meta( self::$post_ids[0], 'delete_key' ); + + $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); + + $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is deleted.' ); + $this->assertNotSame( $posts_meta_before, $posts_meta_after, 'posts-meta last_changed should change when post meta is deleted.' ); + } + + /** + * WP_Query without meta_query should not be invalidated by meta changes. + * + * @ticket 43818 + * @covers WP_Query::get_posts + */ + public function test_wp_query_without_meta_query_not_invalidated_by_meta_change() { + $query_args = array( + 'post_type' => 'post', + 'posts_per_page' => -1, + 'cache_results' => true, + 'fields' => 'ids', + ); + + // Prime the cache. + $q1 = new WP_Query( $query_args ); + $ids_1 = $q1->posts; + + $posts_before = wp_cache_get_last_changed( 'posts' ); + add_post_meta( self::$post_ids[0], 'nonmeta_test', 'val' ); + + $posts_after = wp_cache_get_last_changed( 'posts' ); + $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is added.' ); + + $q2 = new WP_Query( $query_args ); + $ids_2 = $q2->posts; + + $this->assertSameSets( $ids_1, $ids_2, 'Query without meta_query should return same results after meta change.' ); + } + + /** + * WP_Query with meta_query should be invalidated when meta changes. + * + * @ticket 43818 + * @covers WP_Query::get_posts + */ + public function test_wp_query_with_meta_query_is_invalidated_by_meta_change() { + $meta_key = 'meta_query_test_key_' . uniqid(); + add_post_meta( self::$post_ids[0], $meta_key, 'alpha' ); + add_post_meta( self::$post_ids[1], $meta_key, 'beta' ); + + $query_args = array( + 'post_type' => 'post', + 'posts_per_page' => -1, + 'cache_results' => true, + 'fields' => 'ids', + 'meta_query' => array( + array( + 'key' => $meta_key, + 'value' => 'alpha', + 'compare' => '=', + ), + ), + ); + + $q1 = new WP_Query( $query_args ); + $ids_1 = $q1->posts; + $this->assertContains( self::$post_ids[0], $ids_1, 'First query should find post with matching meta.' ); + + update_post_meta( self::$post_ids[0], $meta_key, 'gamma' ); + + $q2 = new WP_Query( $query_args ); + $ids_2 = $q2->posts; + + $this->assertNotContains( self::$post_ids[0], $ids_2, 'Query with meta_query should reflect updated meta value.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_comments_meta_last_changed + */ + public function test_adding_comment_meta_updates_comment_meta_group_not_comment_group() { + $comment_before = wp_cache_get_last_changed( 'comment' ); + $comment_meta_before = wp_cache_get_last_changed( 'comment-meta' ); + + add_comment_meta( self::$comment_ids[0], 'c_key', 'val' ); + + $comment_after = wp_cache_get_last_changed( 'comment' ); + $comment_meta_after = wp_cache_get_last_changed( 'comment-meta' ); + + $this->assertSame( $comment_before, $comment_after, 'comment last_changed should not change when comment meta is added.' ); + $this->assertNotSame( $comment_meta_before, $comment_meta_after, 'comment-meta last_changed should change when comment meta is added.' ); + } + + /** + * @ticket 43818 + * @covers WP_Comment_Query::get_comments + */ + public function test_comment_query_without_meta_query_not_invalidated_by_meta_change() { + $query_args = array( + 'post_id' => self::$post_ids[0], + 'fields' => 'ids', + ); + + // Prime the cache. + $q1 = new WP_Comment_Query( $query_args ); + $r1 = $q1->comments; + + $comment_before = wp_cache_get_last_changed( 'comment' ); + + // Add meta to a comment – must not bump the 'comment' group. + add_comment_meta( self::$comment_ids[0], 'noq_key', 'val' ); + + $comment_after = wp_cache_get_last_changed( 'comment' ); + $this->assertSame( $comment_before, $comment_after, 'comment last_changed should not change when comment meta is added.' ); + + // Re-run the same query – should still hit cache. + $q2 = new WP_Comment_Query( $query_args ); + $r2 = $q2->comments; + + $this->assertSameSets( $r1, $r2, 'Query without meta_query should return same results after comment meta change.' ); + } + + /** + * @ticket 43818 + * @covers WP_Comment_Query::get_comments + */ + public function test_comment_query_with_meta_query_is_invalidated_by_meta_change() { + $meta_key = 'cq_test_key_' . uniqid(); + add_comment_meta( self::$comment_ids[0], $meta_key, 'yes' ); + + $query_args = array( + 'post_id' => self::$post_ids[0], + 'fields' => 'ids', + 'meta_query' => array( + array( + 'key' => $meta_key, + 'value' => 'yes', + 'compare' => '=', + ), + ), + ); + + $q1 = new WP_Comment_Query( $query_args ); + $r1 = $q1->comments; + $this->assertContains( self::$comment_ids[0], $r1, 'First query should find comment with matching meta.' ); + + delete_comment_meta( self::$comment_ids[0], $meta_key ); + + $q2 = new WP_Comment_Query( $query_args ); + $r2 = $q2->comments; + + $this->assertNotContains( self::$comment_ids[0], $r2, 'Query with meta_query should reflect deleted comment meta.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_terms_meta_last_changed + */ + public function test_adding_term_meta_updates_terms_meta_group_not_terms_group() { + $terms_before = wp_cache_get_last_changed( 'terms' ); + $terms_meta_before = wp_cache_get_last_changed( 'terms-meta' ); + + add_term_meta( self::$term_ids[0], 't_key', 'val' ); + + $terms_after = wp_cache_get_last_changed( 'terms' ); + $terms_meta_after = wp_cache_get_last_changed( 'terms-meta' ); + + $this->assertSame( $terms_before, $terms_after, 'terms last_changed should not change when term meta is added.' ); + $this->assertNotSame( $terms_meta_before, $terms_meta_after, 'terms-meta last_changed should change when term meta is added.' ); + } + + /** + * @ticket 43818 + * @covers WP_Term_Query::get_terms + */ + public function test_term_query_without_meta_query_not_invalidated_by_meta_change() { + $terms_before = wp_cache_get_last_changed( 'terms' ); + + add_term_meta( self::$term_ids[0], 'tnoq_key', 'val' ); + + $terms_after = wp_cache_get_last_changed( 'terms' ); + + $this->assertSame( $terms_before, $terms_after, 'terms last_changed should not change when term meta is added.' ); + } + + /** + * @ticket 43818 + * @covers WP_Term_Query::get_terms + */ + public function test_term_query_with_meta_query_is_invalidated_by_meta_change() { + $meta_key = 'tq_test_key_' . uniqid(); + add_term_meta( self::$term_ids[0], $meta_key, 'present' ); + + $query_args = array( + 'taxonomy' => 'category', + 'hide_empty' => false, + 'fields' => 'ids', + 'meta_query' => array( + array( + 'key' => $meta_key, + 'value' => 'present', + 'compare' => '=', + ), + ), + ); + + $q1 = new WP_Term_Query( $query_args ); + $r1 = $q1->get_terms(); + $this->assertContains( self::$term_ids[0], $r1, 'First query should find term with matching meta.' ); + + delete_term_meta( self::$term_ids[0], $meta_key ); + + $q2 = new WP_Term_Query( $query_args ); + $r2 = $q2->get_terms(); + + $this->assertNotContains( self::$term_ids[0], $r2, 'Query with meta_query should reflect deleted term meta.' ); + } + + /** + * @ticket 43818 + * @covers ::wp_cache_set_users_meta_last_changed + */ + public function test_adding_user_meta_updates_users_meta_group_not_users_group() { + $users_before = wp_cache_get_last_changed( 'users' ); + $users_meta_before = wp_cache_get_last_changed( 'users-meta' ); + + add_user_meta( self::$user_ids[0], 'u_key', 'val' ); + + $users_after = wp_cache_get_last_changed( 'users' ); + $users_meta_after = wp_cache_get_last_changed( 'users-meta' ); + + $this->assertSame( $users_before, $users_after, 'users last_changed should not change when user meta is added.' ); + $this->assertNotSame( $users_meta_before, $users_meta_after, 'users-meta last_changed should change when user meta is added.' ); + } + + /** + * @ticket 43818 + * @covers WP_User_Query::query + */ + public function test_user_query_without_meta_query_not_invalidated_by_meta_change() { + $users_before = wp_cache_get_last_changed( 'users' ); + + add_user_meta( self::$user_ids[0], 'unoq_key', 'val' ); + + $users_after = wp_cache_get_last_changed( 'users' ); + + $this->assertSame( $users_before, $users_after, 'users last_changed should not change when user meta is added.' ); + } + + /** + * @ticket 43818 + * @covers WP_User_Query::query + */ + public function test_user_query_with_meta_query_is_invalidated_by_meta_change() { + $meta_key = 'uq_test_key_' . uniqid(); + add_user_meta( self::$user_ids[0], $meta_key, 'found' ); + + $query_args = array( + 'blog_id' => 0, + 'fields' => 'ID', + 'meta_query' => array( + array( + 'key' => $meta_key, + 'value' => 'found', + 'compare' => '=', + ), + ), + ); + + $q1 = new WP_User_Query( $query_args ); + $r1 = array_map( 'intval', $q1->get_results() ); + $this->assertContains( self::$user_ids[0], $r1, 'First query should find user with matching meta.' ); + + delete_user_meta( self::$user_ids[0], $meta_key ); + + $q2 = new WP_User_Query( $query_args ); + $r2 = array_map( 'intval', $q2->get_results() ); + + $this->assertNotContains( self::$user_ids[0], $r2, 'Query with meta_query should reflect deleted user meta.' ); + } } From 5dcc60b618e3909b2c54467dca7a5e18a791fe1e Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Mon, 6 Apr 2026 16:39:36 +0530 Subject: [PATCH 3/3] fix: remove PHPCS errors --- tests/phpunit/tests/cache/metaLastChanged.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/phpunit/tests/cache/metaLastChanged.php b/tests/phpunit/tests/cache/metaLastChanged.php index 9eb5763fbb0b3..bed0018281f0a 100644 --- a/tests/phpunit/tests/cache/metaLastChanged.php +++ b/tests/phpunit/tests/cache/metaLastChanged.php @@ -71,12 +71,12 @@ public function test_wp_cache_set_posts_meta_last_changed_updates_posts_meta_gro * @covers ::wp_cache_set_posts_meta_last_changed */ public function test_adding_post_meta_updates_posts_meta_group_not_posts_group() { - $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_before = wp_cache_get_last_changed( 'posts' ); $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); add_post_meta( self::$post_ids[0], 'test_key', 'test_value' ); - $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_after = wp_cache_get_last_changed( 'posts' ); $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is added.' ); @@ -90,12 +90,12 @@ public function test_adding_post_meta_updates_posts_meta_group_not_posts_group() public function test_updating_post_meta_updates_posts_meta_group_not_posts_group() { add_post_meta( self::$post_ids[0], 'update_key', 'old_value' ); - $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_before = wp_cache_get_last_changed( 'posts' ); $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); update_post_meta( self::$post_ids[0], 'update_key', 'new_value' ); - $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_after = wp_cache_get_last_changed( 'posts' ); $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is updated.' ); @@ -109,12 +109,12 @@ public function test_updating_post_meta_updates_posts_meta_group_not_posts_group public function test_deleting_post_meta_updates_posts_meta_group_not_posts_group() { add_post_meta( self::$post_ids[0], 'delete_key', 'value' ); - $posts_before = wp_cache_get_last_changed( 'posts' ); + $posts_before = wp_cache_get_last_changed( 'posts' ); $posts_meta_before = wp_cache_get_last_changed( 'posts-meta' ); delete_post_meta( self::$post_ids[0], 'delete_key' ); - $posts_after = wp_cache_get_last_changed( 'posts' ); + $posts_after = wp_cache_get_last_changed( 'posts' ); $posts_meta_after = wp_cache_get_last_changed( 'posts-meta' ); $this->assertSame( $posts_before, $posts_after, 'posts last_changed should not change when post meta is deleted.' ); @@ -193,12 +193,12 @@ public function test_wp_query_with_meta_query_is_invalidated_by_meta_change() { * @covers ::wp_cache_set_comments_meta_last_changed */ public function test_adding_comment_meta_updates_comment_meta_group_not_comment_group() { - $comment_before = wp_cache_get_last_changed( 'comment' ); + $comment_before = wp_cache_get_last_changed( 'comment' ); $comment_meta_before = wp_cache_get_last_changed( 'comment-meta' ); add_comment_meta( self::$comment_ids[0], 'c_key', 'val' ); - $comment_after = wp_cache_get_last_changed( 'comment' ); + $comment_after = wp_cache_get_last_changed( 'comment' ); $comment_meta_after = wp_cache_get_last_changed( 'comment-meta' ); $this->assertSame( $comment_before, $comment_after, 'comment last_changed should not change when comment meta is added.' ); @@ -271,12 +271,12 @@ public function test_comment_query_with_meta_query_is_invalidated_by_meta_change * @covers ::wp_cache_set_terms_meta_last_changed */ public function test_adding_term_meta_updates_terms_meta_group_not_terms_group() { - $terms_before = wp_cache_get_last_changed( 'terms' ); + $terms_before = wp_cache_get_last_changed( 'terms' ); $terms_meta_before = wp_cache_get_last_changed( 'terms-meta' ); add_term_meta( self::$term_ids[0], 't_key', 'val' ); - $terms_after = wp_cache_get_last_changed( 'terms' ); + $terms_after = wp_cache_get_last_changed( 'terms' ); $terms_meta_after = wp_cache_get_last_changed( 'terms-meta' ); $this->assertSame( $terms_before, $terms_after, 'terms last_changed should not change when term meta is added.' ); @@ -335,12 +335,12 @@ public function test_term_query_with_meta_query_is_invalidated_by_meta_change() * @covers ::wp_cache_set_users_meta_last_changed */ public function test_adding_user_meta_updates_users_meta_group_not_users_group() { - $users_before = wp_cache_get_last_changed( 'users' ); + $users_before = wp_cache_get_last_changed( 'users' ); $users_meta_before = wp_cache_get_last_changed( 'users-meta' ); add_user_meta( self::$user_ids[0], 'u_key', 'val' ); - $users_after = wp_cache_get_last_changed( 'users' ); + $users_after = wp_cache_get_last_changed( 'users' ); $users_meta_after = wp_cache_get_last_changed( 'users-meta' ); $this->assertSame( $users_before, $users_after, 'users last_changed should not change when user meta is added.' );