fix(2meet-data-optimizer): resync flat rows from native meta after each write
- dual_write / shadow_read no longer write flat in the update_/add_/
delete_{type}_metadata pre-filters, before WP decides whether native
changes at all. added_/updated_/deleted_{type}_meta (priority 1) call
TMDO_Hook_Bus::sync_from_native(), which copies the entity's first native
row (plain SQL, NULL when gone) into flat. Flat now follows native when WP
skips (same value), refuses (non-matching prev_value, add $unique on an
existing key) or deletes (also delete_all: touched IDs are collected from
the meta IDs in delete_{type}_meta), and a flat value left stale by a
direct SQL write heals on the next write.
- update_metadata_by_mid key changes: the old key is remembered in
update_{type}_metadata_by_mid and re-synced too.
- wpdo_after_write / wpdo_after_delete fire from the sync with the same
arguments; aeav_only keeps the pre-filter short-circuit unchanged.
- $internal_ops lock key includes the entity type (user 5 ≠ post 5).
- Live suite resync: 4 entities × 2 modes × 3 scenarios (old code: 16 of
24 FAIL); integration: pre-filter leaves flat to the sync, delete_all
re-syncs every touched user.
- Verify: live resync + shadow_write + serialized + zero_side_effect 33/33
PASS; unit 687 OK, integration 485 OK, phpcs (phpcs.xml) 0, stan OK
- PR: PR-057
This commit is contained in:
@@ -202,14 +202,71 @@ class HookBusIntegrationTest extends TestCase {
|
||||
$this->assertTrue( $result, 'aeav_only must return true to short-circuit EAV write' );
|
||||
}
|
||||
|
||||
public function test_update_writes_flat_and_returns_null_in_dual_write_mode(): void {
|
||||
/**
|
||||
* dual_write: the pre-filter only lets WP write native; flat is synced
|
||||
* from native afterwards (updated_/added_ actions → sync_from_native).
|
||||
*/
|
||||
public function test_update_leaves_flat_to_the_native_sync_in_dual_write_mode(): void {
|
||||
global $wpdb;
|
||||
self::set_user_mode( WPDO_Mode_Manager::MODE_DUAL_WRITE );
|
||||
$GLOBALS['_wp_current_filter'] = 'update_user_metadata';
|
||||
|
||||
$result = WPDO_Hook_Bus::intercept_update( null, 3, 'first_name', 'Carol', '' );
|
||||
|
||||
$this->assertSame( 'Carol', $this->read_flat_first_name( 3 ) );
|
||||
$this->assertNull( $result, 'dual_write must return null so WP continues to write EAV' );
|
||||
$this->assertNull( $this->read_flat_first_name( 3 ), 'no flat write before native is written' );
|
||||
|
||||
$this->with_usermeta(
|
||||
function () use ( $wpdb ) {
|
||||
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 3, 'meta_key' => 'first_name', 'meta_value' => 'Carol' ) );
|
||||
|
||||
WPDO_Hook_Bus::sync_from_native( 'user', 3, 'first_name', 'Carol', 'update' );
|
||||
|
||||
$this->assertSame( 'Carol', $this->read_flat_first_name( 3 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_all passes object ID 0: the delete_ action records the touched
|
||||
* users from the meta IDs, the deleted_ action re-syncs each to NULL.
|
||||
*/
|
||||
public function test_delete_all_resyncs_every_touched_entity_in_dual_write_mode(): void {
|
||||
global $wpdb;
|
||||
self::set_user_mode( WPDO_Mode_Manager::MODE_DUAL_WRITE );
|
||||
$wpdb->insert( self::FLAT_TABLE, array( 'user_id' => 21, 'first_name' => 'A' ) );
|
||||
$wpdb->insert( self::FLAT_TABLE, array( 'user_id' => 22, 'first_name' => 'B' ) );
|
||||
|
||||
$this->with_usermeta(
|
||||
function () use ( $wpdb ) {
|
||||
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 21, 'meta_key' => 'first_name', 'meta_value' => 'A' ) );
|
||||
$mid_a = (int) $wpdb->insert_id;
|
||||
$wpdb->insert( $wpdb->usermeta, array( 'user_id' => 22, 'meta_key' => 'first_name', 'meta_value' => 'B' ) );
|
||||
$mid_b = (int) $wpdb->insert_id;
|
||||
|
||||
$GLOBALS['_wp_current_filter'] = 'delete_user_meta';
|
||||
WPDO_Hook_Bus::before_meta_deleted( array( $mid_a, $mid_b ), 0, 'first_name', '' );
|
||||
$wpdb->query( "DELETE FROM `{$wpdb->usermeta}` WHERE meta_key = 'first_name'" );
|
||||
$GLOBALS['_wp_current_filter'] = 'deleted_user_meta';
|
||||
WPDO_Hook_Bus::on_meta_deleted( array( $mid_a, $mid_b ), 0, 'first_name', '' );
|
||||
|
||||
$this->assertNull( $this->read_flat_first_name( 21 ) );
|
||||
$this->assertNull( $this->read_flat_first_name( 22 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run $fn with a native usermeta table, dropped afterwards.
|
||||
*/
|
||||
private function with_usermeta( callable $fn ): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$wpdb->usermeta}` ( umeta_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL DEFAULT 0, meta_key VARCHAR(255) DEFAULT NULL, meta_value LONGTEXT, PRIMARY KEY (umeta_id) ) ENGINE=InnoDB" );
|
||||
try {
|
||||
$fn();
|
||||
} finally {
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->usermeta}`" );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_update_passthrough_for_unregistered_key(): void {
|
||||
|
||||
@@ -160,7 +160,8 @@ class PostStatusSyncTest extends TestCase {
|
||||
TMDO_Post_Status_Sync::run();
|
||||
$mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' );
|
||||
$mode->setAccessible( true );
|
||||
$mode->setValue( null, array( 'post' => 'dual_write', 'user' => 'disabled', 'term' => 'disabled', 'comment' => 'disabled' ) );
|
||||
// aeav_only: the pre-filter itself writes flat (dual_write syncs from native after the write).
|
||||
$mode->setValue( null, array( 'post' => 'aeav_only', 'user' => 'disabled', 'term' => 'disabled', 'comment' => 'disabled' ) );
|
||||
$GLOBALS['_wp_posts'][3] = (object) array( 'ID' => 3, 'post_type' => 'hp_listing', 'post_status' => 'pending', 'post_date' => '2026-03-01 10:00:00' );
|
||||
$GLOBALS['_wp_current_filter'] = 'update_post_metadata';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user