diff --git a/tests/phpunit/tests/query/deterministicOrdering.php b/tests/phpunit/tests/query/deterministicOrdering.php new file mode 100644 index 0000000000000..7bf9abeecdce3 --- /dev/null +++ b/tests/phpunit/tests/query/deterministicOrdering.php @@ -0,0 +1,365 @@ +post->create( + array( + 'post_title' => "Post $i", + 'post_date' => $identical_date, + ) + ); + } + + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 20; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated post $i content", + ) + ); + } + + // Get first page + $query1 = new WP_Query( + array( + 'orderby' => 'post_date', + 'order' => 'ASC', + 'posts_per_page' => 10, + 'paged' => 1, + ) + ); + + // Get second page + $query2 = new WP_Query( + array( + 'orderby' => 'post_date', + 'order' => 'ASC', + 'posts_per_page' => 10, + 'paged' => 2, + ) + ); + + $page1_ids = wp_list_pluck( $query1->posts, 'ID' ); + $page2_ids = wp_list_pluck( $query2->posts, 'ID' ); + + // Verify no overlap between pages (no duplicates) + $overlap = array_intersect( $page1_ids, $page2_ids ); + $this->assertEmpty( $overlap, 'Pages should not contain duplicate posts' ); + + // Verify total count is correct + $this->assertEquals( 20, $query1->found_posts, 'Total posts should be 20' ); + $this->assertEquals( 10, count( $page1_ids ), 'First page should have 10 posts' ); + $this->assertEquals( 10, count( $page2_ids ), 'Second page should have 10 posts' ); + + // Verify deterministic ordering: same query should return same results + $query1_repeat = new WP_Query( + array( + 'orderby' => 'post_date', + 'order' => 'ASC', + 'posts_per_page' => 10, + 'paged' => 1, + ) + ); + $page1_repeat_ids = wp_list_pluck( $query1_repeat->posts, 'ID' ); + + $this->assertEquals( $page1_ids, $page1_repeat_ids, 'Same query should return same results' ); + } + + /** + * Test that deterministic ordering works with post_title field. + * + * @ticket xxxxx + */ + public function test_deterministic_ordering_with_post_title() { + $identical_title = 'Same Title'; + $post_ids = array(); + + for ( $i = 1; $i <= 15; $i++ ) { + $post_ids[] = self::factory()->post->create( + array( + 'post_title' => $identical_title, + 'post_date' => "2023-01-0$i 10:00:00", + ) + ); + } + + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 15; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated post $i content", + ) + ); + } + + // Get first page + $query1 = new WP_Query( + array( + 'orderby' => 'post_title', + 'order' => 'ASC', + 'posts_per_page' => 8, + 'paged' => 1, + ) + ); + + // Get second page + $query2 = new WP_Query( + array( + 'orderby' => 'post_title', + 'order' => 'ASC', + 'posts_per_page' => 8, + 'paged' => 2, + ) + ); + + $page1_ids = wp_list_pluck( $query1->posts, 'ID' ); + $page2_ids = wp_list_pluck( $query2->posts, 'ID' ); + + // Verify no duplicates across pages + $overlap = array_intersect( $page1_ids, $page2_ids ); + $this->assertEmpty( $overlap, 'Pages should not contain duplicate posts when ordering by title' ); + } + + /** + * Test that deterministic ordering works with DESC order. + * + * @ticket xxxxx + */ + public function test_deterministic_ordering_with_desc_order() { + $identical_date = '2023-01-01 10:00:00'; + $post_ids = array(); + + for ( $i = 1; $i <= 12; $i++ ) { + $post_ids[] = self::factory()->post->create( + array( + 'post_title' => "Post $i", + 'post_date' => $identical_date, + ) + ); + } + + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 12; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated post $i content", + ) + ); + } + + // Get first page with DESC order + $query1 = new WP_Query( + array( + 'orderby' => 'post_date', + 'order' => 'DESC', + 'posts_per_page' => 6, + 'paged' => 1, + ) + ); + + // Get second page with DESC order + $query2 = new WP_Query( + array( + 'orderby' => 'post_date', + 'order' => 'DESC', + 'posts_per_page' => 6, + 'paged' => 2, + ) + ); + + $page1_ids = wp_list_pluck( $query1->posts, 'ID' ); + $page2_ids = wp_list_pluck( $query2->posts, 'ID' ); + + // Verify no duplicates across pages + $overlap = array_intersect( $page1_ids, $page2_ids ); + $this->assertEmpty( $overlap, 'Pages should not contain duplicate posts with DESC order' ); + } + + /** + * Test that deterministic ordering works with array orderby. + * + * @ticket xxxxx + */ + public function test_deterministic_ordering_with_array_orderby() { + $identical_date = '2023-01-01 10:00:00'; + $post_ids = array(); + + for ( $i = 1; $i <= 16; $i++ ) { + $post_ids[] = self::factory()->post->create( + array( + 'post_title' => "Post $i", + 'post_date' => $identical_date, + ) + ); + } + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 15; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated post $i content", + ) + ); + } + + // Test with array orderby + $query1 = new WP_Query( + array( + 'orderby' => array( + 'post_date' => 'ASC', + 'post_title' => 'ASC', + ), + 'posts_per_page' => 8, + 'paged' => 1, + ) + ); + + $query2 = new WP_Query( + array( + 'orderby' => array( + 'post_date' => 'ASC', + 'post_title' => 'ASC', + ), + 'posts_per_page' => 8, + 'paged' => 2, + ) + ); + + $page1_ids = wp_list_pluck( $query1->posts, 'ID' ); + $page2_ids = wp_list_pluck( $query2->posts, 'ID' ); + + // Verify no duplicates across pages + $overlap = array_intersect( $page1_ids, $page2_ids ); + $this->assertEmpty( $overlap, 'Pages should not contain duplicate posts with array orderby' ); + } + + /** + * Test that deterministic ordering doesn't add ID when ID is already present. + * + * @ticket xxxxx + */ + public function test_deterministic_ordering_does_not_duplicate_id() { + $identical_date = '2023-01-01 10:00:00'; + $post_ids = array(); + + for ( $i = 1; $i <= 10; $i++ ) { + $post_ids[] = self::factory()->post->create( + array( + 'post_title' => "Post $i", + 'post_date' => $identical_date, + ) + ); + } + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 10; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated post $i content", + ) + ); + } + + $query = new WP_Query( + array( + 'orderby' => 'ID', + 'order' => 'ASC', + 'posts_per_page' => 10, + ) + ); + + // Should not add duplicate ID ordering + $this->assertStringContainsString( 'ID ASC', $query->request ); + $this->assertStringNotContainsString( 'ID ASC, ID ASC', $query->request ); + } + + /** + * Test that deterministic ordering works with search queries. + * + * @ticket xxxxx + */ + public function test_deterministic_ordering_with_search() { + $identical_date = '2023-01-01 10:00:00'; + $post_ids = array(); + + for ( $i = 1; $i <= 12; $i++ ) { + $post_ids[] = self::factory()->post->create( + array( + 'post_title' => "Test Post $i", + 'post_content' => 'This is a test post', + 'post_date' => $identical_date, + ) + ); + } + + sleep( 1 ); // Ensure modified time changes + // Update every second post if we can trigger the error. + for ( $i = 1; $i <= 12; $i += 2 ) { + wp_update_post( + array( + 'ID' => $post_ids[ $i - 1 ], + 'post_content' => "Updated test post $i content", + ) + ); + } + + // Test with search + $query1 = new WP_Query( + array( + 's' => 'test', + 'orderby' => 'post_date', + 'order' => 'ASC', + 'posts_per_page' => 6, + 'paged' => 1, + ) + ); + + $query2 = new WP_Query( + array( + 's' => 'test', + 'orderby' => 'post_date', + 'order' => 'ASC', + 'posts_per_page' => 6, + 'paged' => 2, + ) + ); + + $page1_ids = wp_list_pluck( $query1->posts, 'ID' ); + $page2_ids = wp_list_pluck( $query2->posts, 'ID' ); + + // Verify no duplicates across pages even with search + $overlap = array_intersect( $page1_ids, $page2_ids ); + $this->assertEmpty( $overlap, 'Pages should not contain duplicate posts even with search' ); + } +}