fix(2meet-data-optimizer): skip zone deletes on tables that were never created
- TMDO_Zone_Router::delete_post() deleted from wp_wpdo_hot_/cold_{post_type}
whenever fields were registered for that zone, so every post delete on a
HivePress site (cold fields registered, module idle, table never created)
logged a "table doesn't exist" error. Hot and cold deletes now also need
the table to exist; module state is not checked, so a table left by a
rolled-back module still loses the rows of deleted posts.
- Live suite zero_side_effect: saving / updating / deleting an hp_listing
issues no query on missing zone tables (covers PR-054 and this PR).
- Verify: ZoneRouterTest 22 OK; live zero_side_effect + shadow_write +
serialized 9/9 PASS (old code: zero_side_effect FAIL); unit 687 OK,
integration 484 OK, phpcs (phpcs.xml) 0, stan OK
- PR: PR-054b
This commit is contained in:
@@ -104,7 +104,10 @@ class TMDO_Zone_Router {
|
||||
*
|
||||
* Callers only need post_id and post_type; zone enumeration and per-zone
|
||||
* error isolation are handled here. Hot and Cold are skipped when the
|
||||
* post_type has no registered fields in that zone (table may not exist).
|
||||
* post_type has no registered fields in that zone, or when the zone table
|
||||
* was never created (fields are registered long before a module runs).
|
||||
* Module state is not checked: a table left by a rolled-back module still
|
||||
* gets its rows for deleted posts removed.
|
||||
*
|
||||
* @param int $post_id Post ID being deleted.
|
||||
* @param string $post_type Post type slug.
|
||||
@@ -113,7 +116,7 @@ class TMDO_Zone_Router {
|
||||
public static function delete_post( int $post_id, string $post_type ): void {
|
||||
$registry = TMDO_Schema_Registry::instance();
|
||||
|
||||
if ( ! empty( $registry->get_hot_columns( $post_type ) ) ) {
|
||||
if ( ! empty( $registry->get_hot_columns( $post_type ) ) && TMDO_Schema_Manager::table_exists( TMDO_Zone_Hot::table( $post_type ) ) ) {
|
||||
try {
|
||||
TMDO_Zone_Hot::delete( $post_id, $post_type );
|
||||
} catch ( \Throwable $e ) {
|
||||
@@ -121,7 +124,7 @@ class TMDO_Zone_Router {
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $registry->get_cold_meta_keys( $post_type ) ) ) {
|
||||
if ( ! empty( $registry->get_cold_meta_keys( $post_type ) ) && TMDO_Schema_Manager::table_exists( TMDO_Zone_Cold::table( $post_type ) ) ) {
|
||||
try {
|
||||
TMDO_Zone_Cold::delete( $post_id, $post_type );
|
||||
} catch ( \Throwable $e ) {
|
||||
|
||||
@@ -38,6 +38,7 @@ Exit code 0 when every test passed, 1 otherwise. Output per test:
|
||||
|---|---|
|
||||
| `shadow_write` | writes in shadow_read reach native meta (user, term, HivePress listing) |
|
||||
| `serialized` | array meta values are stored serialized in flat tables, never "Array" |
|
||||
| `zero_side_effect` | zone tables that were never created are never queried on save / delete |
|
||||
|
||||
## Tools (`tools/`)
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Suite: zone tables that were never created are never queried.
|
||||
*
|
||||
* Bug (dev36, 2026-09-26): the HivePress AddOn registers cold fields, so every
|
||||
* save_post / loop_start (PR-054) and every post delete (PR-054b) queried
|
||||
* wp_wpdo_cold_{post_type} although the cold module was idle and the table
|
||||
* did not exist ("table doesn't exist" DB errors on each write).
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
TMDO_Live::test(
|
||||
'saving and deleting an hp_listing does not touch missing zone tables',
|
||||
static function () {
|
||||
global $wpdb;
|
||||
$tables = array( TMDO_Zone_Cold::table( 'hp_listing' ), TMDO_Zone_Hot::table( 'hp_listing' ) );
|
||||
foreach ( $tables as $table ) {
|
||||
if ( TMDO_Schema_Manager::table_exists( $table ) ) {
|
||||
throw new TMDO_Live_Skip( "{$table} exists on this site" );
|
||||
}
|
||||
}
|
||||
|
||||
$touched = array();
|
||||
$spy = static function ( $sql ) use ( $tables, &$touched ) {
|
||||
foreach ( $tables as $table ) {
|
||||
if ( false !== strpos( $sql, $table ) && 0 !== stripos( ltrim( $sql ), 'SHOW TABLES' ) ) {
|
||||
$touched[] = $sql;
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
};
|
||||
add_filter( 'query', $spy );
|
||||
try {
|
||||
$id = TMDO_Live_Fixtures::post( 'hp_listing' );
|
||||
wp_update_post( array( 'ID' => $id, 'post_title' => 'tmdo-live-updated' ) );
|
||||
wp_delete_post( $id, true );
|
||||
} finally {
|
||||
remove_filter( 'query', $spy );
|
||||
}
|
||||
|
||||
tmdo_live_assert_same( array(), $touched, 'queries on missing zone tables' );
|
||||
}
|
||||
);
|
||||
@@ -21,9 +21,69 @@ class ZoneRouterTest extends TestCase {
|
||||
self::$last_query = '';
|
||||
self::$last_delete = [];
|
||||
self::$get_var_return = null;
|
||||
WPDO_Schema_Manager::flush_table_exists_cache();
|
||||
$this->setup_wpdb_mock();
|
||||
}
|
||||
|
||||
/**
|
||||
* $wpdb mock recording delete() tables; SHOW TABLES reports $existing.
|
||||
*
|
||||
* @param string[] $existing Tables that exist.
|
||||
*/
|
||||
private function delete_logger( array &$captured, array $existing ): void {
|
||||
global $wpdb;
|
||||
$wpdb = new class( $captured, $existing ) {
|
||||
public string $prefix = 'wp_';
|
||||
public array $log;
|
||||
public function __construct( array &$ref, private array $existing ) { $this->log = &$ref; }
|
||||
public function prepare( string $s, mixed ...$a ): string { return str_replace( '%s', (string) ( $a[0] ?? '' ), $s ); }
|
||||
public function query( string $s ): int { return 1; }
|
||||
public function get_var( string $s ): mixed {
|
||||
foreach ( $this->existing as $t ) {
|
||||
if ( "SHOW TABLES LIKE {$t}" === $s ) {
|
||||
return $t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function insert( string $t, array $d, mixed $f = null ): int { return 1; }
|
||||
public function delete( string $table, array $where, array $formats ): void {
|
||||
$this->log[] = $table;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function register_hot_and_cold(): void {
|
||||
$sr = new ReflectionClass( WPDO_Schema_Registry::class );
|
||||
$sr->getProperty( 'instance' )->setValue( null, null );
|
||||
WPDO_Schema_Registry::instance()->register( 'test', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price', 'type' => 'decimal' ) );
|
||||
WPDO_Schema_Registry::instance()->register( 'test', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_images', 'zone' => 'cold' ) );
|
||||
}
|
||||
|
||||
/** Registered fields do not mean the zone tables exist (zero side effect). */
|
||||
public function test_delete_post_skips_hot_and_cold_tables_that_do_not_exist(): void {
|
||||
$this->register_hot_and_cold();
|
||||
$captured = array();
|
||||
$this->delete_logger( $captured, array() );
|
||||
|
||||
WPDO_Zone_Router::delete_post( 3, 'hp_listing' );
|
||||
|
||||
$this->assertNotContains( 'wp_wpdo_hot_hp_listing', $captured );
|
||||
$this->assertNotContains( 'wp_wpdo_cold_hp_listing', $captured );
|
||||
$this->assertContains( 'wp_wpdo_warm', $captured );
|
||||
}
|
||||
|
||||
public function test_delete_post_deletes_cold_rows_when_the_table_exists(): void {
|
||||
$this->register_hot_and_cold();
|
||||
$captured = array();
|
||||
$this->delete_logger( $captured, array( 'wp_wpdo_cold_hp_listing' ) );
|
||||
|
||||
WPDO_Zone_Router::delete_post( 4, 'hp_listing' );
|
||||
|
||||
$this->assertContains( 'wp_wpdo_cold_hp_listing', $captured );
|
||||
$this->assertNotContains( 'wp_wpdo_hot_hp_listing', $captured );
|
||||
}
|
||||
|
||||
private function setup_wpdb_mock(): void {
|
||||
global $wpdb;
|
||||
$wpdb = new class {
|
||||
@@ -204,19 +264,7 @@ class ZoneRouterTest extends TestCase {
|
||||
) );
|
||||
|
||||
$captured = array();
|
||||
global $wpdb;
|
||||
$wpdb = new class( $captured ) {
|
||||
public string $prefix = 'wp_';
|
||||
public array $log;
|
||||
public function __construct( array &$ref ) { $this->log = &$ref; }
|
||||
public function prepare( string $s, mixed ...$a ): string { return $s; }
|
||||
public function query( string $s ): int { return 1; }
|
||||
public function get_var( string $s ): mixed { return null; }
|
||||
public function insert( string $t, array $d, mixed $f = null ): int { return 1; }
|
||||
public function delete( string $table, array $where, array $formats ): void {
|
||||
$this->log[] = $table;
|
||||
}
|
||||
};
|
||||
$this->delete_logger( $captured, array( 'wp_wpdo_hot_hp_listing' ) );
|
||||
|
||||
WPDO_Zone_Router::delete_post( 10, 'hp_listing' );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user