diff --git a/includes/engine/class-tmdo-entity-migration-engine.php b/includes/engine/class-tmdo-entity-migration-engine.php index 39cb1a9..cdf6308 100644 --- a/includes/engine/class-tmdo-entity-migration-engine.php +++ b/includes/engine/class-tmdo-entity-migration-engine.php @@ -176,6 +176,9 @@ final class TMDO_Entity_Migration_Engine { // 標記完成 if ( ! $options['dry_run'] ) { self::mark_migration_completed( $entity_type, $group_name ); + if ( $stats['migrated'] > 0 ) { + self::counts_changed( $entity_type ); + } } $stats['elapsed_sec'] = round( microtime( true ) - $start_time, 2 ); @@ -183,6 +186,19 @@ final class TMDO_Entity_Migration_Engine { return $stats; } + /** + * Rows rebuilt outside the Hook Bus (backfill, drift-check --fix): routed + * post counts cached for the old rows are stale (2.4 PR-129). + * + * @param string $entity_type Entity type. + * @return void + */ + private static function counts_changed( string $entity_type ): void { + if ( 'post' === $entity_type && class_exists( 'TMDO_Post_Query_Router' ) ) { + TMDO_Post_Query_Router::bump_count_version(); + } + } + /** * Rebuild one entity's row of a group from native meta. * @@ -194,7 +210,7 @@ final class TMDO_Entity_Migration_Engine { if ( ! $adapter || empty( $group_fields ) ) { return false; } - return self::migrate_single_entity( + $migrated = self::migrate_single_entity( $entity_type, $group_name, $entity_id, @@ -203,6 +219,10 @@ final class TMDO_Entity_Migration_Engine { $adapter, false ); + if ( $migrated ) { + self::counts_changed( $entity_type ); + } + return $migrated; } /** @@ -409,6 +429,9 @@ final class TMDO_Entity_Migration_Engine { $total = $prev_total + $batch_migrated; $done = count( $entity_ids ) < $batch_size; + if ( $batch_migrated > 0 ) { + self::counts_changed( $entity_type ); + } if ( $done ) { self::mark_migration_completed( $entity_type, $group_name ); diff --git a/tests/integration/PostStatusSyncTest.php b/tests/integration/PostStatusSyncTest.php index fb85738..3aaf18f 100644 --- a/tests/integration/PostStatusSyncTest.php +++ b/tests/integration/PostStatusSyncTest.php @@ -26,6 +26,8 @@ class PostStatusSyncTest extends TestCase { 'includes/engine/class-tmdo-hook-bus.php', 'includes/engine/class-tmdo-post-status-sync.php', 'includes/engine/class-tmdo-entity-migration-engine.php', + 'includes/integrations/class-tmdo-post-fields.php', + 'includes/query/class-tmdo-post-query-router.php', ) as $file ) { require_once $base . $file; } @@ -223,6 +225,40 @@ class PostStatusSyncTest extends TestCase { $this->assertSame( array( 'pending', '2026-03-01 10:00:00' ), $this->copies( self::A )[3] ); } + /** + * Backfills and drift-check --fix rebuild rows outside the Hook Bus: the + * routed counts cached for the old rows must go (2.4 PR-129; dev36: a + * vendor search kept counting 21515 of 21522 matches after a fix). + */ + public function test_rebuilding_post_rows_invalidates_cached_routed_counts(): void { + global $wpdb; + $status = $wpdb->prefix . TMDO_TABLE_PREFIX . 'migration_status'; + $wpdb->query( "CREATE TABLE IF NOT EXISTS `{$wpdb->postmeta}` ( meta_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL DEFAULT 0, meta_key VARCHAR(255) DEFAULT NULL, meta_value LONGTEXT, PRIMARY KEY (meta_id) ) ENGINE=InnoDB" ); + $wpdb->query( "CREATE TABLE IF NOT EXISTS `{$status}` ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, entity_type VARCHAR(50) NOT NULL, group_name VARCHAR(50) NOT NULL, last_id BIGINT UNSIGNED NOT NULL DEFAULT 0, total_migrated BIGINT UNSIGNED NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'pending', started_at DATETIME DEFAULT NULL, completed_at DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY entity_group (entity_type, group_name) ) ENGINE=InnoDB" ); + $version = static fn() => $GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] ?? null; + try { + $wpdb->query( "INSERT INTO `{$wpdb->postmeta}` (post_id, meta_key, meta_value) VALUES (2, 'k', 'a2-native')" ); + + $GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] = 'before'; + TMDO_Entity_Migration_Engine::migrate_group( 'post', 'ps_a', array( 'resume' => false, 'sleep_ms' => 0, 'dry_run' => true ) ); + $this->assertSame( 'before', $version(), 'a dry run writes nothing' ); + + foreach ( array( + 'migrate_entity' => static fn() => TMDO_Entity_Migration_Engine::migrate_entity( 'post', 'ps_a', 2 ), + 'migrate_group' => static fn() => TMDO_Entity_Migration_Engine::migrate_group( 'post', 'ps_a', array( 'resume' => false, 'sleep_ms' => 0 ) ), + 'migrate_group_batch' => static fn() => TMDO_Entity_Migration_Engine::migrate_group_batch( 'post', 'ps_a' ), + ) as $path => $run ) { + TMDO_Entity_Migration_Engine::reset_checkpoint( 'post', 'ps_a' ); + $GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] = 'before'; + $run(); + $this->assertNotSame( 'before', $version(), $path ); + } + } finally { + $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->postmeta}`" ); + $wpdb->query( "DROP TABLE IF EXISTS `{$status}`" ); + } + } + public function test_flat_write_that_creates_a_row_fills_the_copies(): void { TMDO_Post_Status_Sync::run(); $mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' );