582ff13a11
Tests / PHP Lint (pull_request) Successful in 16s
Anti-EAV Lint + Quality Gate / anti-eav-lint (pull_request) Successful in 16s
Tests / Unit Tests (pull_request) Successful in 45s
Tests / PHPCS (pull_request) Successful in 35s
Tests / Integration Tests (pull_request) Successful in 1m38s
Tests / PHPStan (pull_request) Successful in 1m18s
- The PR-079 live router cases ran the HivePress rating sort on the whole site with an unqualified DECIMAL: native rounds 4.5 to 5 and orders its many ties by chance, so the id order passed or failed with the query plan. They now send DECIMAL(10,1), as the HivePress Rating field does, and compare each position's native sort key and found_posts; the flat-first / GROUP BY assertions stay. - Verify: live router 18/18 twice; all suites 56/56 - PR: PR-089b
584 lines
21 KiB
PHP
584 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* Suite: a routed WP_Query returns exactly what the native meta_query returns.
|
|
*
|
|
* Bugs (dev36 qbench, 2026-09-26): the post router pulled registered clauses
|
|
* out of any meta_query and ANDed them, so OR relations (Q04, Q16), queries on
|
|
* several post types (Q08), untyped comparisons on numeric columns (Q14,
|
|
* native compares strings) and orderby on a named clause (Q15) returned other
|
|
* posts than WordPress would; LIKE compared without wildcards (Q06). A key
|
|
* of a second group (wp_core _thumbnail_id, Q05) was never joined, and every
|
|
* routed query carried a GROUP BY although flat rows are one per post.
|
|
*
|
|
* Every case runs the same query twice on fixture posts — router hooks off,
|
|
* then on with post mode shadow_read — and compares the ids in order.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Run a WP_Query restricted to $ids, optionally through the post router.
|
|
*
|
|
* @param array<string, mixed> $args Query args (meta_query, post_type, orderby …).
|
|
* @param int[] $ids Fixture posts to search within.
|
|
* @param bool $routed Hook a TMDO_Post_Query_Router for this query.
|
|
* @return array{ids:int[], routed:bool, sql:string}
|
|
*/
|
|
function tmdo_live_router_query( array $args, array $ids, bool $routed ): array {
|
|
global $wpdb;
|
|
$router = new TMDO_Post_Query_Router();
|
|
if ( $routed ) {
|
|
$router->register_hooks();
|
|
}
|
|
try {
|
|
$wpdb->last_error = '';
|
|
$q = new WP_Query(
|
|
$args + array(
|
|
'post__in' => $ids,
|
|
'post_status' => 'any',
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'ID',
|
|
'order' => 'ASC',
|
|
'fields' => 'ids',
|
|
'no_found_rows' => true,
|
|
'cache_results' => false,
|
|
'suppress_filters' => false,
|
|
)
|
|
);
|
|
} finally {
|
|
tmdo_live_router_unhook( $router );
|
|
}
|
|
tmdo_live_assert_same( '', $wpdb->last_error, 'SQL error' );
|
|
return array(
|
|
'ids' => array_map( 'intval', $q->posts ),
|
|
'routed' => ! empty( $q->get( 'wpdo_post_clauses' ) ) || ! empty( $q->get( 'wpdo_post_sort' ) ),
|
|
'sql' => (string) $q->request,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Remove every hook TMDO_Post_Query_Router::register_hooks() added.
|
|
*
|
|
* @param TMDO_Post_Query_Router $router Router instance.
|
|
*/
|
|
function tmdo_live_router_unhook( TMDO_Post_Query_Router $router ): void {
|
|
foreach ( array( 'pre_get_posts', 'posts_join', 'posts_where', 'posts_orderby', 'posts_request', 'split_the_query', 'found_posts_query' ) as $method ) {
|
|
foreach ( 'posts_request' === $method ? array( 'posts_request', 'posts_request_ids' ) : array( $method ) as $hook ) {
|
|
$priority = has_filter( $hook, array( $router, $method ) );
|
|
if ( false !== $priority ) {
|
|
remove_filter( $hook, array( $router, $method ), $priority );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create posts of $post_type with the given meta, in post mode shadow_read
|
|
* (so the Hook Bus keeps the flat rows in step).
|
|
*
|
|
* @param string $post_type Post type.
|
|
* @param array<string, array<string, mixed>> $posts Label => meta.
|
|
* @return array<string, int> Label => post id.
|
|
*/
|
|
function tmdo_live_router_posts( string $post_type, array $posts ): array {
|
|
$ids = array();
|
|
foreach ( $posts as $label => $meta ) {
|
|
$ids[ $label ] = TMDO_Live_Fixtures::post( $post_type );
|
|
foreach ( $meta as $key => $value ) {
|
|
update_post_meta( $ids[ $label ], $key, $value );
|
|
}
|
|
}
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* Listing fixtures shared by the hp_listing cases.
|
|
*
|
|
* @return array<string, int>
|
|
*/
|
|
function tmdo_live_router_listings(): array {
|
|
foreach ( array( 'hp_price', 'hp_verified', 'hp_featured', 'hp_hourly_rate' ) as $key ) {
|
|
TMDO_Live_Fixtures::field( 'post', $key, 'hp_listing' );
|
|
}
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
return tmdo_live_router_posts(
|
|
'hp_listing',
|
|
array(
|
|
'L1' => array( 'hp_price' => '10000', 'hp_featured' => '1', 'hp_verified' => '0', 'hp_hourly_rate' => '40' ),
|
|
'L2' => array( 'hp_price' => '9500', 'hp_verified' => '1', 'hp_hourly_rate' => '30' ),
|
|
'L3' => array( 'hp_price' => '500', 'hp_featured' => '0', 'hp_hourly_rate' => '45' ),
|
|
'L4' => array( 'hp_price' => '20000', 'tmdo_live_unregistered' => '0' ),
|
|
'L5' => array(),
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Assert the routed query equals native; optionally that it was routed at all.
|
|
*
|
|
* @param array<string, mixed> $args Query args.
|
|
* @param int[] $ids Fixture ids.
|
|
* @param bool|null $expect_routed true / false to assert, null to ignore.
|
|
* @param bool $ordered Compare order too (false when the order has ties).
|
|
*/
|
|
function tmdo_live_router_assert_native( array $args, array $ids, ?bool $expect_routed = null, bool $ordered = true ): void {
|
|
$native = tmdo_live_router_query( $args, $ids, false );
|
|
$routed = tmdo_live_router_query( $args, $ids, true );
|
|
if ( ! $ordered ) {
|
|
sort( $native['ids'] );
|
|
sort( $routed['ids'] );
|
|
}
|
|
tmdo_live_assert_same( $native['ids'], $routed['ids'], 'routed ids vs native' );
|
|
if ( null !== $expect_routed ) {
|
|
tmdo_live_assert_same( $expect_routed, $routed['routed'], 'query routed to the flat table' );
|
|
}
|
|
if ( $routed['routed'] ) {
|
|
tmdo_live_assert_true( false === stripos( $routed['sql'], 'GROUP BY' ), 'routed query adds no GROUP BY: ' . $routed['sql'] );
|
|
}
|
|
}
|
|
|
|
TMDO_Live::test(
|
|
'Q04 OR across registered keys returns the native posts',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
'relation' => 'OR',
|
|
array( 'key' => 'hp_featured', 'value' => '1' ),
|
|
array( 'key' => 'hp_verified', 'value' => '1' ),
|
|
),
|
|
),
|
|
array_values( $l )
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q16 OR mixing a registered and an unregistered key returns the native posts',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
'relation' => 'OR',
|
|
array( 'key' => 'hp_price', 'value' => 1000, 'compare' => '<', 'type' => 'NUMERIC' ),
|
|
array( 'key' => 'tmdo_live_unregistered', 'value' => '0' ),
|
|
),
|
|
),
|
|
array_values( $l )
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q14 untyped comparison on a numeric column keeps string semantics',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array( array( 'key' => 'hp_price', 'value' => '9000', 'compare' => '>' ) ),
|
|
),
|
|
array_values( $l )
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q15 orderby on a named clause keeps the native order, routed',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
'rate' => array( 'key' => 'hp_hourly_rate', 'value' => 50, 'compare' => '<', 'type' => 'NUMERIC' ),
|
|
'price' => array( 'key' => 'hp_price', 'compare' => 'EXISTS', 'type' => 'NUMERIC' ),
|
|
),
|
|
'orderby' => 'price',
|
|
'order' => 'ASC',
|
|
),
|
|
array_values( $l ),
|
|
true
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q08 several post types return the native posts',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_budget', 'hp_request' );
|
|
$r = tmdo_live_router_posts( 'hp_request', array( 'R1' => array( 'hp_budget' => '90000' ) ) );
|
|
update_post_meta( $l['L2'], 'hp_budget', '85000' );
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => array( 'hp_listing', 'hp_request' ),
|
|
'meta_query' => array( array( 'key' => 'hp_budget', 'value' => 80000, 'compare' => '>=', 'type' => 'NUMERIC' ) ),
|
|
),
|
|
array_merge( array_values( $l ), array_values( $r ) )
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'typed AND on registered keys is routed and returns the native posts',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
array( 'key' => 'hp_price', 'value' => array( 9000, 15000 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ),
|
|
array( 'key' => 'hp_verified', 'value' => '1' ),
|
|
),
|
|
),
|
|
array_values( $l ),
|
|
true
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q06 LIKE matches substrings and escapes wildcards, routed',
|
|
static function () {
|
|
// A text key of wp_core (WooCommerce's _sku left the flat tables in 1.2.0).
|
|
TMDO_Live_Fixtures::field( 'post', '_wp_page_template' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
$p = tmdo_live_router_posts(
|
|
'post',
|
|
array(
|
|
'P1' => array( '_wp_page_template' => 'tmdo-live-B1-1' ),
|
|
'P2' => array( '_wp_page_template' => 'tmdo-live-B1-10' ),
|
|
'P3' => array( '_wp_page_template' => 'tmdo-live-B2-1' ),
|
|
)
|
|
);
|
|
foreach ( array( 'B1-1', 'B1_1' ) as $needle ) {
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'post',
|
|
'meta_query' => array( array( 'key' => '_wp_page_template', 'value' => $needle, 'compare' => 'LIKE' ) ),
|
|
),
|
|
array_values( $p ),
|
|
true
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q05 keys of two groups are both joined, routed',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
TMDO_Live_Fixtures::field( 'post', '_thumbnail_id', 'hp_listing' );
|
|
update_post_meta( $l['L1'], '_thumbnail_id', '123' );
|
|
update_post_meta( $l['L3'], '_thumbnail_id', '124' );
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
array( 'key' => 'hp_hourly_rate', 'value' => 42, 'compare' => '>=', 'type' => 'NUMERIC' ),
|
|
array( 'key' => '_thumbnail_id', 'compare' => 'EXISTS' ),
|
|
),
|
|
),
|
|
array_values( $l ),
|
|
true
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'NOT EXISTS with explicit statuses and date order keeps posts without a flat row',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'post_status' => array( 'draft' ),
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'meta_query' => array( array( 'key' => 'hp_featured', 'compare' => 'NOT EXISTS' ) ),
|
|
),
|
|
array_values( $l ),
|
|
true,
|
|
false
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'HivePress sort shape (NOT EXISTS OR EXISTS, named clause) keeps the native order, routed',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
foreach ( array( 'ASC', 'DESC' ) as $order ) {
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_query' => array(
|
|
array(
|
|
'relation' => 'OR',
|
|
'price__order' => array( 'key' => 'hp_price', 'type' => 'DECIMAL', 'compare' => 'NOT EXISTS' ),
|
|
array( 'key' => 'hp_price', 'type' => 'DECIMAL', 'compare' => 'EXISTS' ),
|
|
),
|
|
),
|
|
'orderby' => 'price__order',
|
|
'order' => $order,
|
|
),
|
|
array_values( $l ),
|
|
true
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'Q07 meta_key + meta_value_num keeps the native posts and order, routed',
|
|
static function () {
|
|
$l = tmdo_live_router_listings();
|
|
tmdo_live_router_assert_native(
|
|
array(
|
|
'post_type' => 'hp_listing',
|
|
'meta_key' => 'hp_price',
|
|
'orderby' => 'meta_value_num',
|
|
'order' => 'DESC',
|
|
),
|
|
array_values( $l ),
|
|
true
|
|
);
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a routed full-row search keeps the split query (IDs first) and native found_posts',
|
|
static function () {
|
|
global $wpdb;
|
|
$l = tmdo_live_router_listings();
|
|
$args = array(
|
|
'post_type' => 'hp_listing',
|
|
'post__in' => array_values( $l ),
|
|
'post_status' => 'any',
|
|
'posts_per_page' => 2,
|
|
'orderby' => 'ID',
|
|
'order' => 'ASC',
|
|
'meta_query' => array( array( 'key' => 'hp_price', 'value' => 1000, 'compare' => '>=', 'type' => 'NUMERIC' ) ),
|
|
);
|
|
$native = new WP_Query( $args );
|
|
$router = new TMDO_Post_Query_Router();
|
|
$router->register_hooks();
|
|
try {
|
|
$routed = new WP_Query( $args );
|
|
} finally {
|
|
tmdo_live_router_unhook( $router );
|
|
}
|
|
|
|
tmdo_live_assert_same( wp_list_pluck( $native->posts, 'ID' ), wp_list_pluck( $routed->posts, 'ID' ), 'page of posts' );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
|
|
tmdo_live_assert_true( false !== strpos( $routed->request, 'wpdoflat_' ), 'routed: ' . $routed->request );
|
|
tmdo_live_assert_true( 1 === preg_match( '/^\s*SELECT\s+' . preg_quote( $wpdb->posts, '/' ) . '\.ID\s/', $routed->request ), 'IDs first: ' . $routed->request );
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a filtered search ordered by a flat column drives from the flat table and matches native',
|
|
static function () {
|
|
foreach ( array( 'hp_price', 'hp_hourly_rate' ) as $key ) {
|
|
TMDO_Live_Fixtures::field( 'post', $key, 'hp_listing' );
|
|
}
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
// A price band no seeded listing uses keeps the result to the fixtures.
|
|
tmdo_live_router_posts(
|
|
'hp_listing',
|
|
array(
|
|
'A' => array( 'hp_price' => '9876501', 'hp_hourly_rate' => '12' ),
|
|
'B' => array( 'hp_price' => '9876502', 'hp_hourly_rate' => '15' ),
|
|
'C' => array( 'hp_price' => '9876503' ),
|
|
)
|
|
);
|
|
$args = array(
|
|
'post_type' => 'hp_listing',
|
|
'post_status' => 'any',
|
|
'posts_per_page' => 10,
|
|
'fields' => 'ids',
|
|
'suppress_filters' => false,
|
|
'meta_query' => array( array( 'key' => 'hp_price', 'value' => array( 9876500, 9876510 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ),
|
|
'meta_key' => 'hp_price',
|
|
'orderby' => 'meta_value_num',
|
|
'order' => 'DESC',
|
|
);
|
|
$native = new WP_Query( $args );
|
|
$router = new TMDO_Post_Query_Router();
|
|
$router->register_hooks();
|
|
try {
|
|
$routed = new WP_Query( $args );
|
|
} finally {
|
|
tmdo_live_router_unhook( $router );
|
|
}
|
|
|
|
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), 'ids' );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
|
|
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), 'flat-first: ' . $routed->request );
|
|
}
|
|
);
|
|
|
|
/**
|
|
* HivePress-style sort-only search (rating__order), fields ids, first page.
|
|
* DECIMAL(10,1): what the HivePress Rating field (decimals 1) sends.
|
|
*
|
|
* @param string $order ASC | DESC.
|
|
* @return array<string, mixed> WP_Query args.
|
|
*/
|
|
function tmdo_live_router_rating_sort( string $order ): array {
|
|
return array(
|
|
'post_type' => 'hp_listing',
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => 10,
|
|
'fields' => 'ids',
|
|
'suppress_filters' => false,
|
|
'meta_query' => array(
|
|
array(
|
|
'relation' => 'OR',
|
|
'rating__order' => array( 'key' => 'hp_rating', 'type' => 'DECIMAL(10,1)', 'compare' => 'NOT EXISTS' ),
|
|
array( 'key' => 'hp_rating', 'type' => 'DECIMAL(10,1)', 'compare' => 'EXISTS' ),
|
|
),
|
|
),
|
|
'orderby' => 'rating__order',
|
|
'order' => $order,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The native sort key of each post, in order (null without a rating). On the
|
|
* whole site many listings share a rating and native orders ties by chance,
|
|
* so a page is compared by its keys, not its ids.
|
|
*
|
|
* @param array<int|string> $ids Post ids.
|
|
* @return array<int, string|null>
|
|
*/
|
|
function tmdo_live_router_rating_keys( array $ids ): array {
|
|
global $wpdb;
|
|
$ids = array_map( 'intval', $ids );
|
|
if ( empty( $ids ) ) {
|
|
return array();
|
|
}
|
|
$rows = $wpdb->get_results(
|
|
"SELECT post_id, CAST(meta_value AS DECIMAL(10,1)) AS k FROM {$wpdb->postmeta} WHERE meta_key = 'hp_rating' AND post_id IN (" . implode( ',', $ids ) . ')',
|
|
OBJECT_K
|
|
);
|
|
return array_map( static fn( $id ) => isset( $rows[ $id ] ) ? (string) $rows[ $id ]->k : null, $ids );
|
|
}
|
|
|
|
/**
|
|
* Native and routed runs of the same query.
|
|
*
|
|
* @param array<string, mixed> $args Query args.
|
|
* @return array{0:WP_Query, 1:WP_Query}
|
|
*/
|
|
function tmdo_live_router_native_and_routed( array $args ): array {
|
|
$native = new WP_Query( $args );
|
|
$router = new TMDO_Post_Query_Router();
|
|
$router->register_hooks();
|
|
try {
|
|
$routed = new WP_Query( $args );
|
|
} finally {
|
|
tmdo_live_router_unhook( $router );
|
|
}
|
|
return array( $native, $routed );
|
|
}
|
|
|
|
TMDO_Live::test(
|
|
'a sort-only search drives from the flat table when every post of the type has a flat row (2.2 PR-079)',
|
|
static function () {
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
TMDO_Post_Query_Router::forget_coverage();
|
|
foreach ( array( 'DESC', 'ASC' ) as $order ) {
|
|
list( $native, $routed ) = tmdo_live_router_native_and_routed( tmdo_live_router_rating_sort( $order ) );
|
|
tmdo_live_assert_same( tmdo_live_router_rating_keys( $native->posts ), tmdo_live_router_rating_keys( $routed->posts ), "{$order} sort keys" );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, "{$order} found_posts" );
|
|
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), "{$order} flat-first: " . $routed->request );
|
|
}
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a sort-only search keeps the LEFT JOIN while a post of the type has no flat row (2.2 PR-079)',
|
|
static function () {
|
|
global $wpdb;
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
$bare = TMDO_Live_Fixtures::post( 'hp_listing' );
|
|
$wpdb->delete( TMDO_Schema_Manager::get_table_name( 'post', 'hp_listing_core' ), array( 'post_id' => $bare ) );
|
|
TMDO_Post_Query_Router::forget_coverage();
|
|
try {
|
|
list( $native, $routed ) = tmdo_live_router_native_and_routed( tmdo_live_router_rating_sort( 'DESC' ) );
|
|
} finally {
|
|
TMDO_Post_Query_Router::forget_coverage();
|
|
}
|
|
tmdo_live_assert_same( tmdo_live_router_rating_keys( $native->posts ), tmdo_live_router_rating_keys( $routed->posts ), 'sort keys' );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
|
|
tmdo_live_assert_true( false === strpos( $routed->request, 'STRAIGHT_JOIN' ), 'not flat-first: ' . $routed->request );
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a post created while the router is active gets its empty flat row (2.2 PR-079)',
|
|
static function () {
|
|
global $wpdb;
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
$router = new TMDO_Post_Query_Router();
|
|
$router->register_hooks();
|
|
try {
|
|
$id = TMDO_Live_Fixtures::post( 'hp_listing' );
|
|
} finally {
|
|
tmdo_live_router_unhook( $router );
|
|
}
|
|
$table = TMDO_Schema_Manager::get_table_name( 'post', 'hp_listing_core' );
|
|
tmdo_live_assert_same( '1', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$table}` WHERE post_id = %d", $id ) ), 'flat row created' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a flat-first search drops the GROUP BY WordPress adds for a subquery-only tax query (2.2 PR-079)',
|
|
static function () {
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
TMDO_Post_Query_Router::forget_coverage();
|
|
// Like WooCommerce's product_visibility exclusion on front-end searches: NOT IN
|
|
// is a subquery, yet WordPress groups by posts.ID for any tax query.
|
|
$args = tmdo_live_router_rating_sort( 'DESC' ) + array(
|
|
'tax_query' => array( array( 'taxonomy' => 'hp_listing_category', 'field' => 'term_id', 'terms' => array( 999999999 ), 'operator' => 'NOT IN' ) ),
|
|
);
|
|
list( $native, $routed ) = tmdo_live_router_native_and_routed( $args );
|
|
tmdo_live_assert_same( tmdo_live_router_rating_keys( $native->posts ), tmdo_live_router_rating_keys( $routed->posts ), 'sort keys' );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
|
|
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), 'flat-first: ' . $routed->request );
|
|
tmdo_live_assert_true( false === stripos( $routed->request, 'GROUP BY' ), 'no GROUP BY: ' . $routed->request );
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'a flat-first search keeps the GROUP BY when a tax query joins term relationships (2.2 PR-079)',
|
|
static function () {
|
|
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
|
|
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
|
TMDO_Post_Query_Router::forget_coverage();
|
|
$terms = get_terms( array( 'taxonomy' => 'hp_listing_category', 'number' => 2, 'hide_empty' => true, 'fields' => 'ids' ) );
|
|
if ( count( (array) $terms ) < 2 ) {
|
|
throw new TMDO_Live_Skip( 'needs two used hp_listing_category terms' );
|
|
}
|
|
$args = tmdo_live_router_rating_sort( 'DESC' ) + array(
|
|
'tax_query' => array( array( 'taxonomy' => 'hp_listing_category', 'field' => 'term_id', 'terms' => $terms, 'operator' => 'IN' ) ),
|
|
);
|
|
list( $native, $routed ) = tmdo_live_router_native_and_routed( $args );
|
|
tmdo_live_assert_same( tmdo_live_router_rating_keys( $native->posts ), tmdo_live_router_rating_keys( $routed->posts ), 'sort keys' );
|
|
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, 'found_posts' );
|
|
tmdo_live_assert_true( false !== stripos( $routed->request, 'GROUP BY' ), 'GROUP BY kept: ' . $routed->request );
|
|
}
|
|
);
|