fix(2meet-data-optimizer): upgrade existing flat tables from WP-CLI or WP-Cron only
- a changed group definition no longer runs dbDelta on an existing table inside a web request (seconds to minutes on a large table, and every concurrent request tried it): the group is recorded in tmdo_schema_deferred and tmdo_ensure_group_indexes is scheduled, whose request upgrades it - meanwhile the group stays native: Hook Bus field lookups return no field and the router treats it as drifting; afterwards a drift fix (tmdo_drift_resync) rebuilds the writes that skipped the flat table - new tables are still created at once; aeav_only upgrades at once (the flat table is the only copy); doctor lists deferred groups - Verify: SchemaUpgradeDeferralTest, Hook Bus and router tests, integration 582, unit 701, phpcs, stan; dev36 live cycle in the 2.3 acceptance - PR: PR-107
This commit is contained in:
@@ -948,10 +948,11 @@ final class TMDO_Hook_Bus {
|
||||
*/
|
||||
private static function field_for( string $type, int $object_id, string $meta_key ): ?array {
|
||||
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
|
||||
if ( null === $field_def || 'post' !== $type ) {
|
||||
return $field_def;
|
||||
if ( null !== $field_def && 'post' === $type ) {
|
||||
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key, (string) get_post_type( $object_id ) );
|
||||
}
|
||||
return TMDO_Entity_Registry::get_field( $type, $meta_key, (string) get_post_type( $object_id ) );
|
||||
// A group whose table upgrade waits for WP-CLI / WP-Cron stays native.
|
||||
return null !== $field_def && TMDO_Schema_Manager::is_deferred( $type, (string) $field_def['group'] ) ? null : $field_def;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,9 @@ final class TMDO_Schema_Manager {
|
||||
/** Single cron event that adds declared indexes a visitor request had to leave out. */
|
||||
public const INDEXES_HOOK = 'tmdo_ensure_group_indexes';
|
||||
|
||||
/** Option: "entity/group" → time an existing table's upgrade was left to WP-CLI / WP-Cron. */
|
||||
public const DEFERRED_OPTION = 'tmdo_schema_deferred';
|
||||
|
||||
/** Request-scoped cache for table_exists() — prevents repeated SHOW TABLES per request. */
|
||||
private static array $table_exists_cache = array();
|
||||
|
||||
@@ -95,7 +98,12 @@ final class TMDO_Schema_Manager {
|
||||
return false;
|
||||
}
|
||||
|
||||
$table = self::get_table_name( $entity_type, $group_name );
|
||||
$table = self::get_table_name( $entity_type, $group_name );
|
||||
if ( self::upgrade_waits( $entity_type, $table ) ) {
|
||||
self::defer_upgrade( "{$entity_type}/{$group_name}" );
|
||||
return false;
|
||||
}
|
||||
|
||||
$charset = $wpdb->get_charset_collate();
|
||||
$id_col = $adapter->get_entity_id_column();
|
||||
|
||||
@@ -167,6 +175,7 @@ final class TMDO_Schema_Manager {
|
||||
|
||||
// 記錄 Schema 版本與定義
|
||||
self::store_schema_metadata( $entity_type, $group_name, $schema_hash, $field_definitions );
|
||||
self::end_deferral( $entity_type, $group_name );
|
||||
|
||||
/**
|
||||
* Action: 表建立/升級完成
|
||||
@@ -269,6 +278,73 @@ final class TMDO_Schema_Manager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the group's table upgrade is waiting for WP-CLI / WP-Cron; its
|
||||
* keys then stay native (Hook Bus, query router).
|
||||
*
|
||||
* @param string $entity_type Entity type.
|
||||
* @param string $group_name Group name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_deferred( string $entity_type, string $group_name ): bool {
|
||||
$deferred = get_option( self::DEFERRED_OPTION, array() );
|
||||
return is_array( $deferred ) && isset( $deferred[ "{$entity_type}/{$group_name}" ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an existing table's upgrade waits for WP-CLI / WP-Cron: altering
|
||||
* a large table takes seconds to minutes, as for indexes. Until then the
|
||||
* group reads and writes native meta (is_deferred()). Not in aeav_only,
|
||||
* where the flat table is the only copy: that upgrade runs at once.
|
||||
*
|
||||
* @param string $entity_type Entity type.
|
||||
* @param string $table Flat table name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function upgrade_waits( string $entity_type, string $table ): bool {
|
||||
return self::table_exists( $table )
|
||||
&& ! self::ddl_allowed()
|
||||
&& TMDO_Mode_Manager::MODE_AEAV_ONLY !== TMDO_Mode_Manager::get( $entity_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a deferred table upgrade and have WP-Cron run it.
|
||||
*
|
||||
* @param string $id "entity/group".
|
||||
* @return void
|
||||
*/
|
||||
private static function defer_upgrade( string $id ): void {
|
||||
$deferred = get_option( self::DEFERRED_OPTION, array() );
|
||||
$deferred = is_array( $deferred ) ? $deferred : array();
|
||||
if ( ! isset( $deferred[ $id ] ) ) {
|
||||
$deferred[ $id ] = time();
|
||||
update_option( self::DEFERRED_OPTION, $deferred, true );
|
||||
}
|
||||
if ( ! wp_next_scheduled( self::INDEXES_HOOK ) ) {
|
||||
wp_schedule_single_event( time(), self::INDEXES_HOOK ); // Its request's init runs the upgrade.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A deferred upgrade is done: writes made meanwhile skipped the flat table,
|
||||
* so rebuild it from native (drift fix) a minute later.
|
||||
*
|
||||
* @param string $entity_type Entity type.
|
||||
* @param string $group_name Group name.
|
||||
* @return void
|
||||
*/
|
||||
private static function end_deferral( string $entity_type, string $group_name ): void {
|
||||
if ( ! self::is_deferred( $entity_type, $group_name ) ) {
|
||||
return;
|
||||
}
|
||||
$deferred = (array) get_option( self::DEFERRED_OPTION, array() );
|
||||
unset( $deferred[ "{$entity_type}/{$group_name}" ] );
|
||||
update_option( self::DEFERRED_OPTION, $deferred, true );
|
||||
if ( TMDO_Mode_Manager::writes_to_flat( $entity_type ) && ! wp_next_scheduled( TMDO_Drift_Check::RESYNC_HOOK, array( $entity_type ) ) ) {
|
||||
wp_schedule_single_event( time() + MINUTE_IN_SECONDS, TMDO_Drift_Check::RESYNC_HOOK, array( $entity_type ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key of the MySQL lock held while indexes are added. Lock names are
|
||||
* server-wide, so SQL hashes it with DATABASE() to keep sites apart.
|
||||
|
||||
@@ -634,6 +634,9 @@ class TMDO_Post_Query_Router {
|
||||
if ( TMDO_Mode_Manager::MODE_AEAV_ONLY === TMDO_Mode_Manager::get( 'post' ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( TMDO_Schema_Manager::is_deferred( 'post', $group ) ) {
|
||||
return true; // Table upgrade waiting for WP-CLI / WP-Cron.
|
||||
}
|
||||
$status = get_option( TMDO_Drift_Check::OPTION, array() );
|
||||
$last = is_array( $status ) ? ( $status['post'][ $group ] ?? null ) : null;
|
||||
return is_array( $last ) && (int) ( $last['miss'] ?? 0 ) + (int) ( $last['diff'] ?? 0 ) + (int) ( $last['extra'] ?? 0 ) > 0;
|
||||
|
||||
Reference in New Issue
Block a user