feat(2meet-data-optimizer): support composite indexes on entity groups
- register_group() accepts $args['indexes'] (name => ordered keys); indexes on unknown keys are dropped. Kept outside the field definition, so schema hashes are unchanged. - Schema_Manager::ensure_group_indexes() adds missing cidx_* indexes to the existing flat table in place; the applied definition is remembered in tmdo_entity_indexes so later requests skip SHOW INDEX. - Bench (1M listings, scenario 2): Entity p50 337 ms -> 278 ms with (hp_verified, hp_price), on par with Hot (269 ms). - Verify: core unit 656 / integration 459 green, phpcs clean - PR: PR-027
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Composite indexes declared on a group reach its existing flat table.
|
||||
*
|
||||
* The table already exists with data (its schema hash is unchanged), so the
|
||||
* index is added in place rather than through a table rebuild.
|
||||
*/
|
||||
class GroupCompositeIndexTest extends TestCase {
|
||||
|
||||
private const TABLE = 'wp_itest_wpdo_post_ci_core';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
$base = WPDO_PLUGIN_DIR;
|
||||
foreach ( array(
|
||||
'includes/adapters/interface-entity-adapter.php',
|
||||
'includes/engine/class-tmdo-schema-manager.php',
|
||||
'includes/engine/class-tmdo-entity-registry.php',
|
||||
'includes/adapters/class-tmdo-adapter-post.php',
|
||||
) as $file ) {
|
||||
require_once $base . $file;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
|
||||
$wpdb->query( 'CREATE TABLE `' . self::TABLE . '` ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, ci_flag BIGINT(20) DEFAULT NULL, ci_price DECIMAL(18,6) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (post_id) ) ENGINE=InnoDB' );
|
||||
$wpdb->query( 'INSERT INTO `' . self::TABLE . '` (post_id, ci_flag, ci_price) VALUES (1, 1, 10), (2, 0, 20)' );
|
||||
$GLOBALS['_wp_options'] = array();
|
||||
|
||||
$ref = new ReflectionProperty( WPDO_Schema_Manager::class, 'table_exists_cache' );
|
||||
$ref->setAccessible( true );
|
||||
$ref->setValue( null, array() );
|
||||
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Entity_Registry::register_group(
|
||||
'post',
|
||||
'ci_core',
|
||||
array(
|
||||
array( 'key' => 'ci_flag', 'type' => 'integer' ),
|
||||
array( 'key' => 'ci_price', 'type' => 'decimal' ),
|
||||
),
|
||||
array( 'indexes' => array( 'flag_price' => array( 'ci_flag', 'ci_price' ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
|
||||
WPDO_Entity_Registry::init();
|
||||
}
|
||||
|
||||
/** @return array<string, string[]> index name → ordered columns */
|
||||
private function indexes(): array {
|
||||
global $wpdb;
|
||||
$out = array();
|
||||
foreach ( (array) $wpdb->get_results( 'SHOW INDEX FROM `' . self::TABLE . '`', ARRAY_A ) as $row ) {
|
||||
$out[ $row['Key_name'] ][ (int) $row['Seq_in_index'] - 1 ] = $row['Column_name'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function test_missing_composite_index_is_added_to_the_existing_table(): void {
|
||||
TMDO_Schema_Manager::ensure_group_indexes( 'post', 'ci_core' );
|
||||
|
||||
$this->assertSame( array( 'ci_flag', 'ci_price' ), $this->indexes()['cidx_flag_price'] ?? null );
|
||||
global $wpdb;
|
||||
$this->assertSame( '2', $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TABLE . '`' ), 'data untouched' );
|
||||
}
|
||||
|
||||
public function test_applied_definition_is_remembered_so_later_requests_skip_the_check(): void {
|
||||
TMDO_Schema_Manager::ensure_group_indexes( 'post', 'ci_core' );
|
||||
global $wpdb;
|
||||
$wpdb->query( 'ALTER TABLE `' . self::TABLE . '` DROP INDEX `cidx_flag_price`' );
|
||||
|
||||
TMDO_Schema_Manager::ensure_group_indexes( 'post', 'ci_core' );
|
||||
|
||||
$this->assertArrayNotHasKey( 'cidx_flag_price', $this->indexes(), 'same definition → no SHOW INDEX / ALTER' );
|
||||
$this->assertArrayHasKey( 'post/ci_core', get_option( TMDO_Schema_Manager::INDEXES_OPTION ) );
|
||||
}
|
||||
|
||||
public function test_group_without_indexes_is_left_alone(): void {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Entity_Registry::register_group( 'post', 'ci_core', array( array( 'key' => 'ci_flag', 'type' => 'integer' ) ) );
|
||||
|
||||
TMDO_Schema_Manager::ensure_group_indexes( 'post', 'ci_core' );
|
||||
|
||||
$this->assertSame( array( 'PRIMARY', 'uk_entity' ), array_keys( $this->indexes() ) );
|
||||
$this->assertFalse( get_option( TMDO_Schema_Manager::INDEXES_OPTION ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Composite indexes declared on entity groups.
|
||||
*
|
||||
* Declared through register_group()'s $args like post_types, so a group's
|
||||
* field definition — and its persisted schema hash — never changes when an
|
||||
* index is added.
|
||||
*/
|
||||
class EntityRegistryIndexesTest extends TestCase {
|
||||
|
||||
protected function setUp(): void {
|
||||
TMDO_Entity_Registry::init();
|
||||
TMDO_Entity_Registry::register_adapter( 'post', $this->createMock( TMDO_Entity_Adapter_Interface::class ) );
|
||||
}
|
||||
|
||||
private function fields(): array {
|
||||
return array(
|
||||
array( 'key' => 'x_flag', 'type' => 'integer' ),
|
||||
array( 'key' => 'x_price', 'type' => 'decimal' ),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_declared_indexes_are_kept_per_group(): void {
|
||||
TMDO_Entity_Registry::register_group( 'post', 'x_core', $this->fields(), array( 'indexes' => array( 'flag_price' => array( 'x_flag', 'x_price' ) ) ) );
|
||||
|
||||
$this->assertSame( array( 'flag_price' => array( 'x_flag', 'x_price' ) ), TMDO_Entity_Registry::get_group_indexes( 'post', 'x_core' ) );
|
||||
$this->assertSame( array(), TMDO_Entity_Registry::get_group_indexes( 'post', 'other' ) );
|
||||
}
|
||||
|
||||
public function test_indexes_on_unknown_columns_are_dropped(): void {
|
||||
TMDO_Entity_Registry::register_group( 'post', 'x_core', $this->fields(), array( 'indexes' => array( 'bad' => array( 'x_flag', 'nope' ), 'ok' => array( 'x_price' ) ) ) );
|
||||
|
||||
$this->assertSame( array( 'ok' => array( 'x_price' ) ), TMDO_Entity_Registry::get_group_indexes( 'post', 'x_core' ) );
|
||||
}
|
||||
|
||||
public function test_indexes_leave_the_field_definition_and_hash_alone(): void {
|
||||
TMDO_Entity_Registry::register_group( 'post', 'plain', $this->fields() );
|
||||
TMDO_Entity_Registry::register_group( 'post', 'indexed', $this->fields(), array( 'indexes' => array( 'flag_price' => array( 'x_flag', 'x_price' ) ) ) );
|
||||
|
||||
$plain = TMDO_Schema_Manager::calculate_schema_hash( TMDO_Entity_Registry::get_group_fields( 'post', 'plain' ) );
|
||||
$indexed = TMDO_Schema_Manager::calculate_schema_hash( TMDO_Entity_Registry::get_group_fields( 'post', 'indexed' ) );
|
||||
$this->assertSame( $plain, $indexed );
|
||||
}
|
||||
|
||||
public function test_init_clears_indexes(): void {
|
||||
TMDO_Entity_Registry::register_group( 'post', 'x_core', $this->fields(), array( 'indexes' => array( 'ok' => array( 'x_price' ) ) ) );
|
||||
TMDO_Entity_Registry::init();
|
||||
|
||||
$this->assertSame( array(), TMDO_Entity_Registry::get_group_indexes( 'post', 'x_core' ) );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user