10671e7b78
- TMDO_Post_Query_Router drops SQL_CALC_FOUND_ROWS from routed searches (posts_request) and answers found_posts_query from a COUNT(*) of the same query, cached in the object cache under posts' last_changed. - Hook Bus flat post writes and deletes move posts' last_changed: in aeav_only they skip update_metadata()'s own hooks, so query caches would otherwise outlive the write. - Bench (1M): cached page 8 ms (was 694 / 363 ms for scenarios 1 / 2); after invalidation 8 ms + COUNT 608 / 344 ms, on par with before. - REST X-WP-Total COUNT is on the frozen Hot zone path (ADR-003): not changed. - Verify: core unit 659 / integration 475, HP 142/49, WC 10/22 green, phpcs clean - PR: PR-030
137 lines
6.0 KiB
PHP
137 lines
6.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Routed searches drop SQL_CALC_FOUND_ROWS; the total comes from a COUNT
|
|
* cached until posts' last_changed moves (O-4).
|
|
*
|
|
* Three fr_item posts, prices 10 / 20 / 30.
|
|
*/
|
|
class PostQueryRouterFoundRowsTest extends TestCase {
|
|
|
|
private const TABLE = 'wp_itest_wpdo_post_fr_core';
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
$base = WPDO_PLUGIN_DIR;
|
|
foreach ( array(
|
|
'includes/adapters/interface-entity-adapter.php',
|
|
'includes/engine/class-tmdo-type-caster.php',
|
|
'includes/engine/class-tmdo-entity-registry.php',
|
|
'includes/engine/class-tmdo-mode-manager.php',
|
|
'includes/engine/class-tmdo-schema-manager.php',
|
|
'includes/engine/class-tmdo-cache-orchestrator.php',
|
|
'includes/adapters/class-tmdo-adapter-post.php',
|
|
'includes/integrations/class-tmdo-post-fields.php',
|
|
'includes/engine/class-tmdo-hook-bus.php',
|
|
'includes/engine/class-tmdo-post-status-sync.php',
|
|
'includes/query/class-tmdo-post-query-router.php',
|
|
) as $file ) {
|
|
require_once $base . $file;
|
|
}
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
global $wpdb;
|
|
self::drop();
|
|
$wpdb->query( "CREATE TABLE `{$wpdb->posts}` ( ID BIGINT UNSIGNED NOT NULL, post_type VARCHAR(20) NOT NULL DEFAULT 'post', post_status VARCHAR(20) NOT NULL DEFAULT 'publish', post_date DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', PRIMARY KEY (ID) ) ENGINE=InnoDB" );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TABLE . '` ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, fr_price DECIMAL(18,6) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (post_id) ) ENGINE=InnoDB' );
|
|
$wpdb->query( "INSERT INTO `{$wpdb->posts}` (ID, post_type) VALUES (1, 'fr_item'), (2, 'fr_item'), (3, 'fr_item')" );
|
|
$wpdb->query( 'INSERT INTO `' . self::TABLE . '` (post_id, fr_price) VALUES (1, 10), (2, 20), (3, 30)' );
|
|
|
|
$GLOBALS['_wp_options'] = array();
|
|
$GLOBALS['_wp_cache'] = array();
|
|
$GLOBALS['_wp_last_changed'] = array();
|
|
$GLOBALS['_wp_is_admin'] = false;
|
|
$GLOBALS['_wp_current_filter'] = '';
|
|
$GLOBALS['_wp_post_types'] = array( 1 => 'fr_item', 2 => 'fr_item', 3 => 'fr_item' );
|
|
$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', 'fr_core', array( array( 'key' => 'fr_price', 'type' => 'decimal' ) ), array( 'post_types' => array( 'fr_item' ) ) );
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
self::drop();
|
|
WPDO_Entity_Registry::init();
|
|
$mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' );
|
|
$mode->setAccessible( true );
|
|
$mode->setValue( null, null );
|
|
}
|
|
|
|
private static function drop(): void {
|
|
global $wpdb;
|
|
foreach ( array( $wpdb->posts, self::TABLE ) as $table ) {
|
|
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
|
|
}
|
|
}
|
|
|
|
/** A routed query plus the SQL WordPress would build for it (page size 1). */
|
|
private function routed(): array {
|
|
global $wpdb;
|
|
$query = new WP_Query();
|
|
$query->set( 'post_type', 'fr_item' );
|
|
$query->set( 'post_status', 'publish' );
|
|
$query->set( 'meta_query', array( array( 'key' => 'fr_price', 'value' => 15, 'compare' => '>=' ) ) );
|
|
$router = new TMDO_Post_Query_Router();
|
|
$router->pre_get_posts( $query );
|
|
|
|
$request = "SELECT SQL_CALC_FOUND_ROWS {$wpdb->posts}.ID FROM {$wpdb->posts} " . $router->posts_join( '', $query )
|
|
. " WHERE 1=1 AND {$wpdb->posts}.post_type = 'fr_item' AND (({$wpdb->posts}.post_status = 'publish'))" . $router->posts_where( '', $query )
|
|
. " GROUP BY {$wpdb->posts}.ID ORDER BY {$wpdb->posts}.post_date DESC LIMIT 0, 1";
|
|
return array( $router, $query, $request );
|
|
}
|
|
|
|
public function test_routed_request_drops_sql_calc_found_rows(): void {
|
|
list( $router, $query, $request ) = $this->routed();
|
|
|
|
$sql = $router->posts_request( $request, $query );
|
|
|
|
$this->assertStringNotContainsString( 'SQL_CALC_FOUND_ROWS', $sql );
|
|
$this->assertStringContainsString( 'LIMIT 0, 1', $sql );
|
|
}
|
|
|
|
public function test_unrouted_request_is_left_alone(): void {
|
|
$router = new TMDO_Post_Query_Router();
|
|
$request = 'SELECT SQL_CALC_FOUND_ROWS wp_itest_posts.ID FROM wp_itest_posts WHERE 1=1 LIMIT 0, 10';
|
|
|
|
$this->assertSame( $request, $router->posts_request( $request, new WP_Query() ) );
|
|
$this->assertSame( 'SELECT FOUND_ROWS()', $router->found_posts_query( 'SELECT FOUND_ROWS()', new WP_Query() ) );
|
|
}
|
|
|
|
public function test_total_is_counted_once_and_served_from_cache_until_posts_change(): void {
|
|
global $wpdb;
|
|
list( $router, $query, $request ) = $this->routed();
|
|
$router->posts_request( $request, $query );
|
|
|
|
$this->assertSame( '2', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ) );
|
|
|
|
$wpdb->query( 'UPDATE `' . self::TABLE . '` SET fr_price = 5 WHERE post_id = 2' );
|
|
$this->assertSame( '2', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'cached' );
|
|
|
|
wp_cache_set_posts_last_changed();
|
|
$this->assertSame( '1', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'recounted' );
|
|
}
|
|
|
|
public function test_flat_writes_and_deletes_move_posts_last_changed(): void {
|
|
$GLOBALS['_wp_current_filter'] = 'update_post_metadata';
|
|
$before = wp_cache_get_last_changed( 'posts' );
|
|
WPDO_Hook_Bus::intercept_update( null, 1, 'fr_price', '11', '' );
|
|
$after_write = wp_cache_get_last_changed( 'posts' );
|
|
|
|
$GLOBALS['_wp_current_filter'] = 'delete_post_metadata';
|
|
WPDO_Hook_Bus::intercept_delete( null, 1, 'fr_price', '', false );
|
|
|
|
$this->assertNotSame( $before, $after_write );
|
|
$this->assertNotSame( $after_write, wp_cache_get_last_changed( 'posts' ) );
|
|
}
|
|
}
|