feat(2meet-data-optimizer): group-scoped native write-back and group retirement
- TMDO_Native_Restorer::run() takes an optional group; the CLI gains `bridge-restore-native <entity> --group=<g>`. - New retire(): once native meta holds every non-NULL value of a group's flat table (checked against the persisted field definitions), forget its registry_meta row and rename the table to <table>_retired_<Ymd>, so orphan restore no longer routes its keys to the flat table. Refused while a plugin still registers the group or any value differs. CLI: `--group=<g> --retire`. - For retiring the user `sso` group (SSO AddOn deprecation). - Verify: core unit 659 / integration 479 green, phpcs clean - PR: PR-042
This commit is contained in:
@@ -20,6 +20,10 @@
|
||||
* $demote the entity then moves to dual_write, which keeps both copies in
|
||||
* step from then on.
|
||||
*
|
||||
* A single group can be written back on its own ($group) and then retired:
|
||||
* retire() forgets its registry_meta row and keeps the flat table under a
|
||||
* `_retired_<Ymd>` name, so orphan restore never routes its keys again.
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
*/
|
||||
|
||||
@@ -47,9 +51,10 @@ final class TMDO_Native_Restorer {
|
||||
* @param bool $dry_run Count only; write nothing.
|
||||
* @param int $batch Rows per batch.
|
||||
* @param bool $demote After a complete run, switch the entity to dual_write.
|
||||
* @param string $group Only this group ('' = every group).
|
||||
* @return array{groups: array<string, array{rows:int, written:int}>, demoted: bool}
|
||||
*/
|
||||
public static function run( string $entity_type, bool $dry_run = false, int $batch = 500, bool $demote = false ): array {
|
||||
public static function run( string $entity_type, bool $dry_run = false, int $batch = 500, bool $demote = false, string $group = '' ): array {
|
||||
global $wpdb;
|
||||
|
||||
$report = array(
|
||||
@@ -73,6 +78,9 @@ final class TMDO_Native_Restorer {
|
||||
$cursor = is_array( $cursor ) ? $cursor : array();
|
||||
|
||||
$tables = self::group_tables( $entity_type );
|
||||
if ( '' !== $group ) {
|
||||
$tables = array_intersect_key( $tables, array( $group => true ) );
|
||||
}
|
||||
foreach ( $tables as $group => $table ) {
|
||||
$entry = array(
|
||||
'rows' => 0,
|
||||
@@ -121,6 +129,74 @@ final class TMDO_Native_Restorer {
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget a group that no plugin registers any more, once native meta holds
|
||||
* every non-NULL value of its flat table (run() with $group first).
|
||||
*
|
||||
* @param string $entity_type Entity type.
|
||||
* @param string $group Group name.
|
||||
* @return array{status:string, mismatches:int, archived_table:string}
|
||||
* status: retired | registered (a plugin still registers it) |
|
||||
* missing (no registry_meta row or flat table) | mismatch (native differs).
|
||||
*/
|
||||
public static function retire( string $entity_type, string $group ): array {
|
||||
global $wpdb;
|
||||
$result = array(
|
||||
'status' => 'missing',
|
||||
'mismatches' => 0,
|
||||
'archived_table' => '',
|
||||
);
|
||||
$restored = array_map( static fn( $r ) => $r['entity_type'] . '/' . $r['group'], TMDO_Entity_Registry::get_restored_groups() );
|
||||
if ( TMDO_Entity_Registry::get_group_fields( $entity_type, $group ) && ! in_array( "{$entity_type}/{$group}", $restored, true ) ) {
|
||||
$result['status'] = 'registered';
|
||||
return $result;
|
||||
}
|
||||
$adapter = TMDO_Entity_Registry::get_adapter( $entity_type );
|
||||
$meta_table = $wpdb->prefix . TMDO_TABLE_PREFIX . 'registry_meta';
|
||||
$table = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
|
||||
$defs = $wpdb->get_var( $wpdb->prepare( "SELECT field_definitions FROM `{$meta_table}` WHERE entity_type = %s AND group_name = %s", $entity_type, $group ) );
|
||||
$fields = json_decode( (string) $defs, true );
|
||||
if ( ! $adapter || ! is_array( $fields ) || null === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$native_table = $adapter->get_native_meta_table();
|
||||
$id_col = $adapter->get_entity_id_column();
|
||||
foreach ( (array) $wpdb->get_results( "SELECT * FROM `{$table}`", ARRAY_A ) as $row ) {
|
||||
foreach ( $fields as $field ) {
|
||||
$col = TMDO_Schema_Manager::sanitize_column_name( (string) ( $field['key'] ?? '' ) );
|
||||
if ( '' === $col || ! isset( $row[ $col ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$want = (string) maybe_serialize( TMDO_Type_Caster::from_db( $row[ $col ], $field ) );
|
||||
$native = $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM `{$native_table}` WHERE `{$id_col}` = %d AND meta_key = %s", (int) $row[ $id_col ], $field['key'] ) );
|
||||
if ( array( $want ) !== $native ) {
|
||||
++$result['mismatches'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $result['mismatches'] ) {
|
||||
$result['status'] = 'mismatch';
|
||||
return $result;
|
||||
}
|
||||
|
||||
$archived = $table . '_retired_' . gmdate( 'Ymd' );
|
||||
$wpdb->query( "RENAME TABLE `{$table}` TO `{$archived}`" );
|
||||
$wpdb->delete(
|
||||
$meta_table,
|
||||
array(
|
||||
'entity_type' => $entity_type,
|
||||
'group_name' => $group,
|
||||
)
|
||||
);
|
||||
delete_transient( 'tmdo_orphan_native_keys' );
|
||||
TMDO_Schema_Manager::flush_table_exists_cache();
|
||||
|
||||
$result['status'] = 'retired';
|
||||
$result['archived_table'] = $archived;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered groups of the entity whose flat table exists.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user