query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' ); $wpdb->query( 'CREATE TABLE `' . self::TABLE . "` ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, rc_price DECIMAL(18,6) DEFAULT NULL, post_status VARCHAR(20) DEFAULT NULL, post_date DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (post_id), KEY idx_post_status_date_id (post_status, post_date, post_id) ) ENGINE=InnoDB" ); $wpdb->query( 'INSERT INTO `' . self::TABLE . "` (post_id, rc_price, post_status, post_date) VALUES (1, 10, 'publish', '2026-01-01 00:00:00')" ); $GLOBALS['_wp_options'] = array( TMDO_Post_Status_Sync::OPTION => array( 'rc_core' => 'ready' ) ); $GLOBALS['_wp_is_admin'] = false; $ref = new ReflectionProperty( WPDO_Schema_Manager::class, 'table_exists_cache' ); $ref->setAccessible( true ); $ref->setValue( null, array() ); $mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' ); $mode->setAccessible( true ); $mode->setValue( null, array( 'post' => 'aeav_only', 'user' => 'disabled', 'term' => 'disabled', 'comment' => 'disabled' ) ); TMDO_Post_Query_Router::flush_copy_cache(); WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Entity_Registry::register_group( 'post', 'rc_core', array( array( 'key' => 'rc_price', 'type' => 'decimal' ) ), array( 'post_types' => array( 'rc_item' ) ) ); } protected function tearDown(): void { global $wpdb; $wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' ); WPDO_Entity_Registry::init(); $mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' ); $mode->setAccessible( true ); $mode->setValue( null, null ); } /** * The full request WordPress would build for the routed search, after posts_request. * * @param string $extra_join Another JOIN (a tax query's). * @param string $ties ID tie-breaks plugins appended. */ private function request( string $extra_join = '', string $ties = ', {posts}.ID DESC, {posts}.ID DESC' ): string { global $wpdb; $ties = str_replace( '{posts}', $wpdb->posts, $ties ); $query = new WP_Query(); foreach ( array( 'post_type' => 'rc_item', 'post_status' => 'publish', 'meta_query' => array( array( 'key' => 'rc_price', 'value' => array( 5, 20 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ), ) as $k => $v ) { $query->set( $k, $v ); } $router = new TMDO_Post_Query_Router(); $router->pre_get_posts( $query ); $request = 'SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts ' . $router->posts_join( $extra_join, $query ) . ' WHERE 1=1 ' . $router->posts_where( " AND wp_posts.post_status = 'publish'", $query ) . " GROUP BY {$wpdb->posts}.ID ORDER BY " . $router->posts_orderby( 'wp_posts.post_date DESC', $query ) . $ties . ' LIMIT 0, 10'; return $router->posts_request( $request, $query ); } /** * 2.4 PR-128: with GROUP BY or a posts.ID tie-break MariaDB sorts every * match (dev36, 235k of 1.3M listings: 1.7 s); without them it walks * (post_status, post_date, post_id) backwards and stops at the LIMIT (3 ms). */ public function test_copy_route_orders_by_the_flat_date_and_post_id_without_group_by(): void { $sql = $this->request(); $this->assertStringNotContainsString( 'GROUP BY', $sql, 'one flat row per post: nothing to collapse' ); $this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC, ' . self::ALIAS . '.`post_id` DESC LIMIT 0, 10', $sql ); } public function test_copy_route_keeps_each_tie_break_direction(): void { $this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC, ' . self::ALIAS . '.`post_id` ASC LIMIT 0, 10', $this->request( '', ', {posts}.ID ASC' ) ); $this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC LIMIT 0, 10', $this->request( '', '' ) ); } public function test_copy_route_leaves_posts_id_inside_expressions_alone(): void { global $wpdb; $sql = $this->request( '', ', FIELD({posts}.ID,5,9) DESC, ({posts}.ID IN (3,4)) DESC, {posts}.ID DESC' ); $this->assertStringEndsWith( "`post_date` DESC, FIELD({$wpdb->posts}.ID,5,9) DESC, ({$wpdb->posts}.ID IN (3,4)) DESC, " . self::ALIAS . '.`post_id` DESC LIMIT 0, 10', $sql ); } public function test_copy_route_keeps_group_by_with_another_join(): void { global $wpdb; $sql = $this->request( " LEFT JOIN wp_term_relationships ON ({$wpdb->posts}.ID = wp_term_relationships.object_id)" ); $this->assertStringContainsString( "GROUP BY {$wpdb->posts}.ID", $sql ); } /** @return array{join:string, where:string, orderby:string} */ private function sql( array $vars ): array { $query = new WP_Query(); foreach ( array_merge( array( 'post_type' => 'rc_item', 'post_status' => 'publish', 'meta_query' => array( array( 'key' => 'rc_price', 'value' => array( 5, 20 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ), ), $vars ) as $k => $v ) { $query->set( $k, $v ); } $router = new TMDO_Post_Query_Router(); $router->pre_get_posts( $query ); return array( 'join' => $router->posts_join( '', $query ), 'where' => $router->posts_where( " AND wp_posts.post_status = 'publish'", $query ), 'orderby' => $router->posts_orderby( 'wp_posts.post_date DESC', $query ), ); } private function assert_legacy_shape( array $sql ): void { $this->assertStringContainsString( 'LEFT JOIN', $sql['join'] ); $this->assertStringNotContainsString( self::ALIAS . '.`post_status`', $sql['where'] ); $this->assertSame( 'wp_posts.post_date DESC', $sql['orderby'] ); } public function test_ready_copies_filter_status_and_order_by_date_on_the_flat_table(): void { $sql = $this->sql( array( 'order' => 'ASC' ) ); $this->assertStringContainsString( 'INNER JOIN', $sql['join'] ); $this->assertStringContainsString( self::ALIAS . ".`post_status` IN ('publish')", $sql['where'] ); $this->assertStringContainsString( "wp_posts.post_status = 'publish'", $sql['where'], 'WordPress status clause stays' ); $this->assertSame( self::ALIAS . '.`post_date` ASC', $sql['orderby'] ); } public function test_several_explicit_statuses_are_kept(): void { $sql = $this->sql( array( 'post_status' => array( 'publish', 'private' ), 'orderby' => 'date' ) ); $this->assertStringContainsString( self::ALIAS . ".`post_status` IN ('publish','private')", $sql['where'] ); $this->assertSame( self::ALIAS . '.`post_date` DESC', $sql['orderby'] ); } public function test_copies_not_marked_ready_keep_the_legacy_shape(): void { $GLOBALS['_wp_options'] = array( TMDO_Post_Status_Sync::OPTION => array( 'rc_core' => 'columns' ) ); $this->assert_legacy_shape( $this->sql( array() ) ); } public function test_rows_with_missing_copies_keep_the_legacy_shape(): void { global $wpdb; $wpdb->query( 'INSERT INTO `' . self::TABLE . '` (post_id, rc_price) VALUES (2, 12)' ); $this->assert_legacy_shape( $this->sql( array() ) ); } public function test_other_orderings_or_implicit_statuses_keep_the_legacy_shape(): void { $this->assert_legacy_shape( $this->sql( array( 'orderby' => 'title' ) ) ); $this->assert_legacy_shape( $this->sql( array( 'post_status' => '' ) ) ); $this->assert_legacy_shape( $this->sql( array( 'post_status' => 'any' ) ) ); } public function test_not_exists_keeps_the_legacy_shape(): void { // The INNER JOIN would drop posts without a flat row, which NOT EXISTS must match. $this->assert_legacy_shape( $this->sql( array( 'meta_query' => array( array( 'key' => 'rc_price', 'compare' => 'NOT EXISTS' ) ) ) ) ); } }