fix(2meet-data-optimizer): archive sweep copies each trashed meta row once
- sweep() skips meta rows already in wpdo_archive (NOT EXISTS on original_meta_id): it copied the same trashed meta again every day and wpdo_archive has no unique key (dev20: 300 rows for 150 meta rows) - TMDO_Zone_Archive::remove_duplicates() keeps the first copy per original_meta_id (rows without one are left alone); install() runs it, so `wp tmdo install` cleans existing sites - wpdo_archive gets KEY idx_original_meta for the lookup (new installs; existing sites via `wp tmdo install`). SCHEMA_VERSION is not bumped, so no web request runs dbDelta - Verify: ZoneArchiveIntegrationTest (2 new); dev20 read-only check: 150 of 300 rows would be removed; unit 709, integration 608, phpcs 0, stan OK - PR: PR-136 (manual F-6)
This commit is contained in:
@@ -217,6 +217,15 @@ class TMDO_Installer {
|
||||
// Idempotent — only creates tables that don't already exist.
|
||||
self::install_v2_tables();
|
||||
|
||||
// 1.4.2: archive sweeps before this release copied trashed meta daily.
|
||||
if ( class_exists( 'TMDO_Zone_Archive' ) ) {
|
||||
global $wpdb;
|
||||
$archive = TMDO_Zone_Archive::table();
|
||||
if ( $archive === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $archive ) ) ) ) {
|
||||
TMDO_Zone_Archive::remove_duplicates();
|
||||
}
|
||||
}
|
||||
|
||||
update_option( 'wpdo_db_version', self::SCHEMA_VERSION );
|
||||
|
||||
// Register wpdo_features with autoload=no so it doesn't inflate every page load.
|
||||
@@ -1440,7 +1449,8 @@ class TMDO_Installer {
|
||||
original_meta_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_post_id (post_id),
|
||||
KEY idx_post_type_date (post_type, archived_at)
|
||||
KEY idx_post_type_date (post_type, archived_at),
|
||||
KEY idx_original_meta (original_meta_id)
|
||||
) {$charset};";
|
||||
|
||||
foreach ( $sqls as $sql ) {
|
||||
|
||||
@@ -210,7 +210,11 @@ class TMDO_Zone_Archive {
|
||||
global $wpdb;
|
||||
|
||||
$cutoff = gmdate( 'Y-m-d H:i:s', time() - ( $days * DAY_IN_SECONDS ) );
|
||||
// Rows already archived are skipped: the sweep used to copy the same
|
||||
// trashed meta again every day (2.4.2 PR-136).
|
||||
$table = self::table();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal self::table()
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT pm.meta_id, pm.post_id, pm.meta_key, pm.meta_value, p.post_type
|
||||
@@ -218,12 +222,14 @@ class TMDO_Zone_Archive {
|
||||
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE p.post_status = 'trash'
|
||||
AND p.post_modified_gmt < %s
|
||||
AND NOT EXISTS ( SELECT 1 FROM `{$table}` a WHERE a.original_meta_id = pm.meta_id )
|
||||
LIMIT %d",
|
||||
$cutoff,
|
||||
$limit
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
if ( ! $rows ) {
|
||||
return 0;
|
||||
@@ -245,6 +251,21 @@ class TMDO_Zone_Archive {
|
||||
return count( $entries );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete archive rows that copy the same original meta row again, keeping
|
||||
* the first copy (sweeps before 1.4.2 archived trashed meta every day).
|
||||
* Rows without an original meta id are left alone.
|
||||
*
|
||||
* @return int Rows deleted.
|
||||
*/
|
||||
public static function remove_duplicates(): int {
|
||||
global $wpdb;
|
||||
$table = self::table();
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal self::table()
|
||||
$deleted = $wpdb->query( "DELETE FROM `{$table}` WHERE original_meta_id > 0 AND id NOT IN ( SELECT keep_id FROM ( SELECT MIN(id) AS keep_id FROM `{$table}` WHERE original_meta_id > 0 GROUP BY original_meta_id ) AS k )" );
|
||||
return is_int( $deleted ) ? $deleted : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive statistics.
|
||||
*
|
||||
|
||||
@@ -45,6 +45,55 @@ class ZoneArchiveIntegrationTest extends TestCase {
|
||||
$GLOBALS['_wp_postmeta'] = [];
|
||||
}
|
||||
|
||||
// ── sweep / duplicates (2.4.2 PR-136, manual F-6) ─────────────────────
|
||||
|
||||
/** Posts and postmeta for sweep(): post 10 trashed 200 days ago, post 11 published. */
|
||||
private function seed_trashed_post(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->posts}`" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->postmeta}`" );
|
||||
$wpdb->query( "CREATE TABLE `{$wpdb->posts}` ( ID BIGINT UNSIGNED NOT NULL, post_type VARCHAR(20) NOT NULL DEFAULT 'post', post_status VARCHAR(20) NOT NULL DEFAULT 'publish', post_modified_gmt DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', PRIMARY KEY (ID) ) ENGINE=InnoDB" );
|
||||
$wpdb->query( "CREATE TABLE `{$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" );
|
||||
$old = gmdate( 'Y-m-d H:i:s', time() - 200 * DAY_IN_SECONDS );
|
||||
$wpdb->query( "INSERT INTO `{$wpdb->posts}` VALUES (10, 'hp_listing', 'trash', '{$old}'), (11, 'hp_listing', 'publish', '{$old}')" );
|
||||
$wpdb->query( "INSERT INTO `{$wpdb->postmeta}` (post_id, meta_key, meta_value) VALUES (10, 'hp_price', '5'), (10, 'hp_rating', '4'), (11, 'hp_price', '9')" );
|
||||
}
|
||||
|
||||
private function drop_posts(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->posts}`" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->postmeta}`" );
|
||||
}
|
||||
|
||||
private function archive_rows(): int {
|
||||
global $wpdb;
|
||||
return (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TABLE . '`' );
|
||||
}
|
||||
|
||||
public function test_sweep_copies_each_trashed_meta_row_once(): void {
|
||||
$this->seed_trashed_post();
|
||||
try {
|
||||
$this->assertSame( 2, WPDO_Zone_Archive::sweep( 90, false ) );
|
||||
$this->assertSame( 0, WPDO_Zone_Archive::sweep( 90, false ), 'already archived' );
|
||||
$this->assertSame( 2, $this->archive_rows() );
|
||||
} finally {
|
||||
$this->drop_posts();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_remove_duplicates_keeps_the_first_copy_of_each_meta_row(): void {
|
||||
WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_price', '5', 101 );
|
||||
WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_price', '5', 101 );
|
||||
WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_rating', '4', 102 );
|
||||
WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_rating', '4', 102 );
|
||||
WPDO_Zone_Archive::archive( 12, 'hp_listing', 'hp_price', '7' ); // original_meta_id 0: no identity, kept.
|
||||
WPDO_Zone_Archive::archive( 12, 'hp_listing', 'hp_price', '7' );
|
||||
|
||||
$this->assertSame( 2, WPDO_Zone_Archive::remove_duplicates() );
|
||||
$this->assertSame( 4, $this->archive_rows() );
|
||||
$this->assertSame( 0, WPDO_Zone_Archive::remove_duplicates() );
|
||||
}
|
||||
|
||||
// ── archive / get ─────────────────────────────────────────────────────
|
||||
|
||||
public function test_archive_stores_plain_entry(): void {
|
||||
|
||||
Reference in New Issue
Block a user