diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 3833a7a5a5..a52b5a484e 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -3179,36 +3179,36 @@ class WP_Query { $cached_results = wp_cache_get( $cache_key, 'post-queries', false, $cache_found ); if ( $cached_results ) { - if ( 'ids' === $q['fields'] ) { - /** @var int[] */ - $this->posts = array_map( 'intval', $cached_results['posts'] ); - } else { - _prime_post_caches( $cached_results['posts'], $q['update_post_term_cache'], $q['update_post_meta_cache'] ); - /** @var WP_Post[] */ - $this->posts = array_map( 'get_post', $cached_results['posts'] ); - } + /** @var int[] */ + $post_ids = array_map( 'intval', $cached_results['posts'] ); - $this->post_count = count( $this->posts ); + $this->post_count = count( $post_ids ); $this->found_posts = $cached_results['found_posts']; $this->max_num_pages = $cached_results['max_num_pages']; if ( 'ids' === $q['fields'] ) { + $this->posts = $post_ids; + return $this->posts; } elseif ( 'id=>parent' === $q['fields'] ) { + _prime_post_parents_caches( $post_ids ); + /** @var int[] */ - $post_parents = array(); + $post_parents = wp_cache_get_multiple( $post_ids, 'post_parent' ); - foreach ( $this->posts as $key => $post ) { + foreach ( $post_parents as $id => $post_parent ) { $obj = new stdClass(); - $obj->ID = (int) $post->ID; - $obj->post_parent = (int) $post->post_parent; + $obj->ID = (int) $id; + $obj->post_parent = (int) $post_parent; - $this->posts[ $key ] = $obj; - - $post_parents[ $obj->ID ] = $obj->post_parent; + $this->posts[] = $obj; } return $post_parents; + } else { + _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + /** @var WP_Post[] */ + $this->posts = array_map( 'get_post', $post_ids ); } } } @@ -3256,6 +3256,8 @@ class WP_Query { $post_parents[ (int) $post->ID ] = (int) $post->post_parent; $post_ids[] = (int) $post->ID; } + // Prime post parent caches, so that on second run, there is not another database query. + wp_cache_add_multiple( $post_parents, 'post_parent' ); if ( $q['cache_results'] && $id_query_is_cacheable ) { $cache_value = array( diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 4b6bc14acf..bb799802ad 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7169,7 +7169,7 @@ function _get_non_cached_ids( $object_ids, $cache_group ) { $cache_values = wp_cache_get_multiple( $object_ids, $cache_group ); foreach ( $cache_values as $id => $value ) { - if ( ! $value ) { + if ( false === $value ) { $non_cached_ids[] = (int) $id; } } diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 8a3bc56656..ea81bbd045 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7262,6 +7262,7 @@ function clean_post_cache( $post ) { wp_cache_delete( $post->ID, 'posts' ); wp_cache_delete( $post->ID, 'post_meta' ); + wp_cache_delete( $post->ID, 'post_parent' ); clean_object_term_cache( $post->ID, $post->post_type ); @@ -7795,6 +7796,31 @@ function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache } } +/** + * Prime post parent caches. + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param int[] $ids ID list. + */ +function _prime_post_parents_caches( array $ids ) { + global $wpdb; + + $non_cached_ids = _get_non_cached_ids( $ids, 'post_parent' ); + if ( ! empty( $non_cached_ids ) ) { + $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.ID, $wpdb->posts.post_parent FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) ); + + if ( $fresh_posts ) { + $post_parent_data = array(); + foreach ( $fresh_posts as $fresh_post ) { + $post_parent_data[ (int) $fresh_post->ID ] = (int) $fresh_post->post_parent; + } + + wp_cache_add_multiple( $post_parent_data, 'post_parent' ); + } + } +} + /** * Adds a suffix if any trashed posts have a given slug. * diff --git a/tests/phpunit/tests/post/primePostParentsCaches.php b/tests/phpunit/tests/post/primePostParentsCaches.php new file mode 100644 index 0000000000..b423a2f87c --- /dev/null +++ b/tests/phpunit/tests/post/primePostParentsCaches.php @@ -0,0 +1,132 @@ +post->create_many( 3 ); + } + + /** + * @ticket 59188 + */ + public function test_prime_post_parents_caches() { + $post_id = self::$posts[0]; + + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( array( $post_id ) ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries.' ); + $this->assertSameSets( array( 0 ), wp_cache_get_multiple( array( $post_id ), 'post_parent' ), 'Array of parent ids' ); + } + + /** + * @ticket 59188 + */ + public function test_prime_post_parents_caches_multiple() { + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( self::$posts ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries.' ); + $this->assertSameSets( array( 0, 0, 0 ), wp_cache_get_multiple( self::$posts, 'post_parent' ), 'Array of parent ids' ); + } + + /** + * @ticket 59188 + */ + public function test_prime_post_parents_caches_multiple_runs() { + _prime_post_parents_caches( self::$posts ); + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( self::$posts ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 0, $num_queries, 'Unexpected number of queries.' ); + } + + /** + * @ticket 59188 + */ + public function test_prime_post_parents_caches_update() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_parent' => self::$posts[0], + ) + ); + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( array( $page_id ) ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries on first run' ); + $this->assertSameSets( array( self::$posts[0] ), wp_cache_get_multiple( array( $page_id ), 'post_parent' ), 'Array of parent ids with post 0 as parent' ); + + wp_update_post( + array( + 'ID' => $page_id, + 'post_parent' => self::$posts[1], + ) + ); + + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( array( $page_id ) ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries on second run' ); + $this->assertSameSets( array( self::$posts[1] ), wp_cache_get_multiple( array( $page_id ), 'post_parent' ), 'Array of parent ids with post 1 as parent' ); + + } + + /** + * @ticket 59188 + */ + public function test_prime_post_parents_caches_delete() { + $parent_page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + ) + ); + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_parent' => $parent_page_id, + ) + ); + $before_num_queries = get_num_queries(); + _prime_post_parents_caches( array( $page_id ) ); + $num_queries = get_num_queries() - $before_num_queries; + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries on first run' ); + $this->assertSameSets( array( $parent_page_id ), wp_cache_get_multiple( array( $page_id ), 'post_parent' ), 'Array of parent ids with post 0 as parent' ); + + wp_delete_post( $parent_page_id, true ); + + $this->assertSame( 1, $num_queries, 'Unexpected number of queries on second run' ); + $this->assertSameSets( array( false ), wp_cache_get_multiple( array( $page_id ), 'post_parent' ), 'Array of parent ids with false values' ); + } +} diff --git a/tests/phpunit/tests/query/cacheResults.php b/tests/phpunit/tests/query/cacheResults.php index b3923d8229..3e2d405850 100644 --- a/tests/phpunit/tests/query/cacheResults.php +++ b/tests/phpunit/tests/query/cacheResults.php @@ -710,7 +710,7 @@ class Test_Query_CacheResults extends WP_UnitTestCase { $query2->query( $args ); $queries_after = get_num_queries(); - $this->assertSame( $queries_before, $queries_after ); + $this->assertSame( 1, $queries_after - $queries_before ); $this->assertCount( 5, $query1->posts ); $this->assertCount( 5, $query2->posts ); $this->assertSame( $query1->found_posts, $query2->found_posts ); @@ -726,6 +726,110 @@ class Test_Query_CacheResults extends WP_UnitTestCase { $this->assertNotEquals( $query1->posts, $query2->posts ); } + + /** + * @ticket 59188 + */ + public function test_query_cache_unprimed_parents() { + $args = array( + 'cache_results' => true, + 'fields' => 'id=>parent', + ); + $query1 = new WP_Query(); + $query1->query( $args ); + + $post_ids = wp_list_pluck( $query1->posts, 'ID' ); + wp_cache_delete_multiple( $post_ids, 'post_parent' ); + + $queries_before = get_num_queries(); + $query2 = new WP_Query(); + $query2->query( $args ); + $queries_after = get_num_queries(); + + $this->assertSame( 1, $queries_after - $queries_before, 'There should be only one query to prime parents' ); + $this->assertCount( 5, $query1->posts, 'There should be only 5 posts returned on first query' ); + $this->assertCount( 5, $query2->posts, 'There should be only 5 posts returned on second query' ); + $this->assertSame( $query1->found_posts, $query2->found_posts, 'Found posts should match on second query' ); + } + + /** + * @ticket 59188 + */ + public function test_query_cache_update_parent() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_parent' => self::$pages[0], + ) + ); + $args = array( + 'cache_results' => true, + 'post_type' => 'page', + 'fields' => 'id=>parent', + 'post__in' => array( + $page_id, + ), + ); + $query1 = new WP_Query(); + $query1->query( $args ); + + wp_update_post( + array( + 'ID' => $page_id, + 'post_parent' => self::$pages[1], + ) + ); + + $queries_before = get_num_queries(); + $query2 = new WP_Query(); + $query2->query( $args ); + $queries_after = get_num_queries(); + + $this->assertSame( self::$pages[0], $query1->posts[0]->post_parent, 'Check post parent on first query' ); + $this->assertSame( self::$pages[1], $query2->posts[0]->post_parent, 'Check post parent on second query' ); + $this->assertSame( 2, $queries_after - $queries_before, 'There should be 2 queries, one for id=>parent' ); + $this->assertSame( $query1->found_posts, $query2->found_posts, 'Found posts should match on second query' ); + } + + /** + * @ticket 59188 + */ + public function test_query_cache_delete_parent() { + $parent_page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + ) + ); + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_parent' => $parent_page_id, + ) + ); + $args = array( + 'cache_results' => true, + 'post_type' => 'page', + 'fields' => 'id=>parent', + 'post__in' => array( + $page_id, + ), + ); + $query1 = new WP_Query(); + $query1->query( $args ); + + wp_delete_post( $parent_page_id, true ); + + $queries_before = get_num_queries(); + $query2 = new WP_Query(); + $query2->query( $args ); + $queries_after = get_num_queries(); + + $this->assertSame( $parent_page_id, $query1->posts[0]->post_parent, 'Check post parent on first query' ); + $this->assertSame( 0, $query2->posts[0]->post_parent, 'Check post parent on second query' ); + $this->assertSame( 2, $queries_after - $queries_before, 'There should be 2 queries, one for id=>parent' ); + $this->assertSame( $query1->found_posts, $query2->found_posts, 'Found posts should match on second query' ); + } + /** * @ticket 22176 */