From e486ac07458d36182ee75091eac06f9a99a5ae2a Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Thu, 21 Sep 2023 19:32:55 +0000 Subject: [PATCH] Query: Improved handling of filtered queries in WP_Query. The `WP_Query` class enables developers to customize queries using filters like `posts_fields_request`, `posts_request`, and `the_posts`, which can modify both the queried fields and retrieved post objects. In some cases with these filters, incomplete or invalid post objects lacking essential data may arise. To address this, if any of these filters are active during a query, the `get_posts` method now avoids caching post objects with the usual `update_post_caches` function call, opting for a call to `_prime_post_caches` instead. This may occasionally trigger new database queries to prime the post data cache. While this enhancement may result in rare additional database queries, it ensures that invalid post objects aren't cached, prioritizing data consistency and integrity in filtered query scenarios. Props saulirajala, spacedmonkey, flixos90, mukesh27, peterwilsoncc. Fixes #58599. git-svn-id: https://develop.svn.wordpress.org/trunk@56656 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-query.php | 14 +++- tests/phpunit/tests/query/cacheResults.php | 79 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 458b7898c2..3833a7a5a5 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -3270,10 +3270,11 @@ class WP_Query { return $post_parents; } + $is_unfiltered_query = $old_request == $this->request && "{$wpdb->posts}.*" === $fields; + if ( null === $this->posts ) { $split_the_query = ( - $old_request == $this->request - && "{$wpdb->posts}.*" === $fields + $is_unfiltered_query && ( wp_using_ext_object_cache() || ( ! empty( $limits ) && $q['posts_per_page'] < 500 ) @@ -3337,6 +3338,8 @@ class WP_Query { $this->posts = array_map( 'get_post', $this->posts ); } + $unfiltered_posts = $this->posts; + if ( $q['cache_results'] && $id_query_is_cacheable && ! $cache_found ) { $post_ids = wp_list_pluck( $this->posts, 'ID' ); @@ -3529,7 +3532,12 @@ class WP_Query { $this->posts = array_map( 'get_post', $this->posts ); if ( $q['cache_results'] ) { - update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + if ( $is_unfiltered_query && $unfiltered_posts === $this->posts ) { + update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + } else { + $post_ids = wp_list_pluck( $this->posts, 'ID' ); + _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + } } /** @var WP_Post */ diff --git a/tests/phpunit/tests/query/cacheResults.php b/tests/phpunit/tests/query/cacheResults.php index 6ddaffd273..b3923d8229 100644 --- a/tests/phpunit/tests/query/cacheResults.php +++ b/tests/phpunit/tests/query/cacheResults.php @@ -1079,6 +1079,85 @@ class Test_Query_CacheResults extends WP_UnitTestCase { $this->assertNotSame( $query1->found_posts, $query2->found_posts ); } + /** + * @ticket 58599 + */ + public function test_query_posts_fields_request() { + global $wpdb; + + $args = array( + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'no_found_rows' => true, + ); + + add_filter( 'posts_fields_request', array( $this, 'filter_posts_fields_request' ) ); + + $before = get_num_queries(); + $query1 = new WP_Query(); + $posts1 = $query1->query( $args ); + $after = get_num_queries(); + + foreach ( $posts1 as $_post ) { + $this->assertNotSame( get_post( $_post->ID )->post_content, $_post->post_content ); + } + + $this->assertSame( 2, $after - $before, 'There should only be 2 queries run, one for request and one prime post objects.' ); + + $this->assertStringContainsString( + "SELECT $wpdb->posts.*", + $wpdb->last_query, + 'Check that _prime_post_caches is called.' + ); + } + + public function filter_posts_fields_request( $fields ) { + global $wpdb; + return "{$wpdb->posts}.ID"; + } + + /** + * @ticket 58599 + * @dataProvider data_query_filter_posts_results + */ + public function test_query_filter_posts_results( $filter ) { + global $wpdb; + + $args = array( + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'no_found_rows' => true, + ); + + add_filter( $filter, array( $this, 'filter_posts_results' ) ); + + $before = get_num_queries(); + $query1 = new WP_Query(); + $posts1 = $query1->query( $args ); + $after = get_num_queries(); + + $this->assertCount( 1, $posts1 ); + + $this->assertSame( 2, $after - $before, 'There should only be 2 queries run, one for request and one prime post objects.' ); + + $this->assertStringContainsString( + "SELECT $wpdb->posts.*", + $wpdb->last_query, + 'Check that _prime_post_caches is called.' + ); + } + + public function filter_posts_results() { + return array( get_post( self::$posts[0] ) ); + } + + public function data_query_filter_posts_results() { + return array( + array( 'posts_results' ), + array( 'the_posts' ), + ); + } + /** * @ticket 22176 */