fix(2meet-data-optimizer): give new posts' flat rows their status/date copies; sync drops cached counts

- add_flat_rows (wp_insert_post, after transition_post_status) inserts the
  copies with the empty row: a NULL copy turned the copy route off for the
  whole group until the next sync
- wp tmdo post-status-sync bumps the routed count version (not on dry runs)
- Test bootstrap: minimal WP_Post stub
- Found in the 2.4 correctness review (residual risks)
- Verify: 2 new integration tests; integration 606, unit 704, phpcs 0, stan OK
- PR: PR-133
This commit is contained in:
2026-09-28 00:32:59 +08:00
parent 80343ec282
commit 3e96357459
5 changed files with 53 additions and 2 deletions
@@ -163,6 +163,10 @@ final class TMDO_Post_Status_Sync {
}
self::set_state( $group, 'ready' );
}
// Routed counts cached while the copies were stale must go.
if ( ! $dry_run && class_exists( 'TMDO_Post_Query_Router' ) ) {
TMDO_Post_Query_Router::bump_count_version();
}
return $report;
}
@@ -125,9 +125,17 @@ class TMDO_Post_Query_Router {
global $wpdb;
foreach ( TMDO_Post_Fields::groups_for_post_type( $post->post_type ) as $group ) {
$table = TMDO_Schema_Manager::get_table_name( 'post', $group );
if ( TMDO_Schema_Manager::table_exists( $table ) ) {
$wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `{$table}` (`post_id`) VALUES (%d)", (int) $post_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
continue;
}
// With the status / date copies: this runs after transition_post_status,
// and one NULL copy turns the copy route off for the whole group.
$copies = class_exists( 'TMDO_Post_Status_Sync' ) ? TMDO_Post_Status_Sync::insert_columns( (int) $post_id, $group ) : array();
if ( $copies ) {
$wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `{$table}` (`post_id`, `post_status`, `post_date`) VALUES (%d, %s, %s)", (int) $post_id, $copies['post_status'], $copies['post_date'] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
continue;
}
$wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `{$table}` (`post_id`) VALUES (%d)", (int) $post_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
}
@@ -133,6 +133,22 @@ class PostQueryRouterCopyTest extends TestCase {
$this->assertSame( 'wp_posts.post_title ASC', $router->posts_orderby( 'wp_posts.post_title ASC', $query ) );
}
/**
* wp_insert_post runs after transition_post_status: a new post's empty flat
* row must carry the copies itself, or one NULL turns the copy route off for
* the whole group (2.4 correctness review).
*/
public function test_a_new_post_gets_its_flat_row_with_the_copies(): void {
global $wpdb;
$post = new WP_Post( (object) array( 'ID' => 7, 'post_type' => 'rc_item', 'post_status' => 'draft', 'post_date' => '2026-02-02 00:00:00' ) );
$GLOBALS['_wp_posts'][7] = $post;
( new TMDO_Post_Query_Router() )->add_flat_rows( 7, $post, false );
$this->assertSame( array( 'post_status' => 'draft', 'post_date' => '2026-02-02 00:00:00' ), $wpdb->get_row( 'SELECT post_status, post_date FROM `' . self::TABLE . '` WHERE post_id = 7', ARRAY_A ) );
unset( $GLOBALS['_wp_posts'][7] );
}
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)" );
+10
View File
@@ -132,6 +132,16 @@ class PostStatusSyncTest extends TestCase {
$this->assertSame( 2, $report['ps_a']['mismatched'] );
}
/** Routed counts cached while copies were stale are dropped by a sync (2.4 correctness review). */
public function test_a_sync_invalidates_cached_routed_counts_but_a_dry_run_does_not(): void {
$GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] = 'before';
TMDO_Post_Status_Sync::run( true );
$this->assertSame( 'before', $GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] );
TMDO_Post_Status_Sync::run();
$this->assertNotSame( 'before', $GLOBALS['_wp_last_changed'][ TMDO_Post_Query_Router::COUNT_VERSION_GROUP ] );
}
public function test_copies_use_the_posts_collation_so_they_compare_with_posts(): void {
global $wpdb;
TMDO_Post_Status_Sync::run();
+13
View File
@@ -354,6 +354,19 @@ if ( ! function_exists( 'get_term_meta' ) ) { function get_term_meta( int $tid,
if ( ! function_exists( 'update_term_meta' ) ) { function update_term_meta( int $tid, string $key, $value, $prev = '' ): bool { $GLOBALS['_wp_termmeta'][ $tid ][ $key ] = $value; return true; } }
if ( ! function_exists( 'get_comment_meta' ) ) { function get_comment_meta( int $cid, string $key = '', bool $single = false ) { return $GLOBALS['_wp_commentmeta'][ $cid ][ $key ] ?? ( $single ? '' : [] ); } }
if ( ! function_exists( 'update_comment_meta' ) ) { function update_comment_meta( int $cid, string $key, $value, $prev = '' ): bool { $GLOBALS['_wp_commentmeta'][ $cid ][ $key ] = $value; return true; } }
if ( ! class_exists( 'WP_Post' ) ) {
final class WP_Post {
public $ID = 0;
public $post_type = 'post';
public $post_status = 'publish';
public $post_date = '0000-00-00 00:00:00';
public function __construct( $post ) {
foreach ( get_object_vars( $post ) as $k => $v ) {
$this->$k = $v;
}
}
}
}
if ( ! function_exists( 'get_post' ) ) { function get_post( $post_id ) { return $GLOBALS['_wp_posts'][ (int) $post_id ] ?? null; } }
if ( ! function_exists( 'get_post_type' ) ) { function get_post_type( $post_id ) { return $GLOBALS['_wp_post_types'][ (int) $post_id ] ?? false; } }
if ( ! function_exists( 'get_post_status' ) ) { function get_post_status( $post_id ) { return $GLOBALS['_wp_post_status'][ (int) $post_id ] ?? 'publish'; } }