fix(2meet-data-optimizer): do not recreate a deleted entity's flat row
- Deleting a post / user / term / comment drops its flat row in the adapter delete hook, then WordPress deletes each meta row; sync_from_native upserted NULL for each and brought the row back (512 orphan rows on dev36). With no native value it now only clears the column on an existing row - Verify: tests/live/run.php resync (25/25, entity deletes in dual_write and shadow_read leave no flat row); mut.php T8 flat_row NO-ROW; integration 516 - PR: PR-057b
This commit is contained in:
@@ -201,7 +201,8 @@ final class TMDO_Hook_Bus {
|
|||||||
* (NULL when native has no row). Runs in dual_write / shadow_read only,
|
* (NULL when native has no row). Runs in dual_write / shadow_read only,
|
||||||
* after WP wrote native, so flat always ends up equal to native — also
|
* after WP wrote native, so flat always ends up equal to native — also
|
||||||
* when WP skipped or refused the write, and when an earlier direct SQL
|
* when WP skipped or refused the write, and when an earlier direct SQL
|
||||||
* write had left flat stale.
|
* write had left flat stale. A NULL never creates a row: deleting an
|
||||||
|
* entity drops its flat row before WP deletes the meta rows one by one.
|
||||||
*
|
*
|
||||||
* @param string $type Entity type.
|
* @param string $type Entity type.
|
||||||
* @param int $object_id Entity ID.
|
* @param int $object_id Entity ID.
|
||||||
@@ -222,7 +223,9 @@ final class TMDO_Hook_Bus {
|
|||||||
|
|
||||||
$before = self::maybe_read_before_value( $type, $object_id, $meta_key, $field_def, $op );
|
$before = self::maybe_read_before_value( $type, $object_id, $meta_key, $field_def, $op );
|
||||||
$native = self::native_first_value( $type, $object_id, $meta_key );
|
$native = self::native_first_value( $type, $object_id, $meta_key );
|
||||||
$result = self::perform_upsert( $type, $object_id, $meta_key, null === $native ? null : maybe_unserialize( $native ), $field_def );
|
$result = null === $native
|
||||||
|
? self::clear_column( $type, $object_id, $meta_key, $field_def )
|
||||||
|
: self::perform_upsert( $type, $object_id, $meta_key, maybe_unserialize( $native ), $field_def );
|
||||||
|
|
||||||
if ( 'delete' === $op ) {
|
if ( 'delete' === $op ) {
|
||||||
do_action( 'wpdo_after_delete', $type, $object_id, $meta_key, $meta_value, $result, $delete_all, $before );
|
do_action( 'wpdo_after_delete', $type, $object_id, $meta_key, $meta_value, $result, $delete_all, $before );
|
||||||
@@ -522,6 +525,34 @@ final class TMDO_Hook_Bus {
|
|||||||
return 'flat';
|
return 'flat';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a flat column to NULL on the entity's existing row; never insert one.
|
||||||
|
*
|
||||||
|
* @param string $type Entity type.
|
||||||
|
* @param int $entity_id Entity ID.
|
||||||
|
* @param string $meta_key Meta key.
|
||||||
|
* @param array<string, mixed> $field_def Registered field.
|
||||||
|
* @return bool|null Null when there is no adapter or table.
|
||||||
|
*/
|
||||||
|
private static function clear_column( string $type, int $entity_id, string $meta_key, array $field_def ): ?bool {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$adapter = TMDO_Entity_Registry::get_adapter( $type );
|
||||||
|
$table = TMDO_Schema_Manager::get_table_name( $type, $field_def['group'] );
|
||||||
|
if ( ! $adapter || ! TMDO_Schema_Manager::table_exists( $table ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$col = TMDO_Schema_Manager::sanitize_column_name( $meta_key );
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||||
|
$result = $wpdb->update( $table, array( $col => null ), array( $adapter->get_entity_id_column() => $entity_id ) );
|
||||||
|
|
||||||
|
TMDO_Cache_Orchestrator::invalidate( $type, $entity_id, $field_def['group'] );
|
||||||
|
self::touch_posts_last_changed( $type );
|
||||||
|
|
||||||
|
return false !== $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 執行 Upsert 操作
|
* 執行 Upsert 操作
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -256,6 +256,24 @@ class HookBusIntegrationTest extends TestCase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deleting an entity drops its flat row first (adapter delete hook), then
|
||||||
|
* WordPress deletes each meta row; syncing those deletions must not bring
|
||||||
|
* the row back as all NULL.
|
||||||
|
*/
|
||||||
|
public function test_sync_of_a_deleted_key_does_not_recreate_a_missing_row(): void {
|
||||||
|
global $wpdb;
|
||||||
|
self::set_user_mode( WPDO_Mode_Manager::MODE_DUAL_WRITE );
|
||||||
|
|
||||||
|
$this->with_usermeta(
|
||||||
|
function () use ( $wpdb ) {
|
||||||
|
WPDO_Hook_Bus::sync_from_native( 'user', 31, 'first_name', 'Gone', 'delete' );
|
||||||
|
|
||||||
|
$this->assertSame( '0', $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::FLAT_TABLE . '` WHERE user_id = 31' ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run $fn with a native usermeta table, dropped afterwards.
|
* Run $fn with a native usermeta table, dropped afterwards.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -110,3 +110,29 @@ foreach ( array( 'dual_write', 'shadow_read' ) as $tmdo_live_mode ) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TMDO_Live::test(
|
||||||
|
'deleting an entity leaves no flat row behind (dual_write and shadow_read)',
|
||||||
|
static function () {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||||||
|
$deleters = array(
|
||||||
|
'user' => static fn( int $id ) => wp_delete_user( $id ),
|
||||||
|
'post' => static fn( int $id ) => wp_delete_post( $id, true ),
|
||||||
|
'term' => static fn( int $id ) => wp_delete_term( $id, 'hp_listing_category' ),
|
||||||
|
'comment' => static fn( int $id ) => wp_delete_comment( $id, true ),
|
||||||
|
);
|
||||||
|
foreach ( array( 'dual_write', 'shadow_read' ) as $mode ) {
|
||||||
|
foreach ( tmdo_live_resync_cases() as $label => $case ) {
|
||||||
|
list( $type, $key, $make, $pt ) = $case;
|
||||||
|
TMDO_Live_Fixtures::field( $type, $key, $pt );
|
||||||
|
TMDO_Live_Fixtures::use_modes( array( $type => $mode ) );
|
||||||
|
$id = $make();
|
||||||
|
update_metadata( $type, $id, $key, '41' );
|
||||||
|
|
||||||
|
$deleters[ $type ]( $id );
|
||||||
|
|
||||||
|
tmdo_live_assert_same( 'NO-ROW', TMDO_Live_Fixtures::flat( $type, $id, $key, $pt ), "{$mode} {$label}: flat row after delete" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user