Skip to content

Commit

Permalink
BUGS-6563: Provides command to add a Primary Key table and notice to …
Browse files Browse the repository at this point in the history
…users if needed.
  • Loading branch information
Ryan Wagner committed Sep 19, 2023
1 parent 49e3b28 commit 0a0b5a5
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
135 changes: 135 additions & 0 deletions inc/class-cli-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,141 @@ public function delete( $args, $assoc_args ) {
}
}
}

/**
* Set id as primary key in the Native PHP Sessions plugin table.
*
* @subcommand add-index
*/
public function add_index( $args, $assoc_arc ) {
global $wpdb;
$unprefixed_table = 'pantheon_sessions';
$table = $wpdb->base_prefix . $unprefixed_table;
$temp_clone_table = $wpdb->base_prefix . 'sessions_temp_clone';

// If the command has been run multiple times and there is already a
// temp_clone table, drop it.
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $temp_clone_table ) );

if ( $wpdb->get_var( $query ) == $temp_clone_table ) {
$query = "DROP TABLE {$temp_clone_table};";
$wpdb->query( $query );
}

if ( ! PANTHEON_SESSIONS_ENABLED ) {
WP_CLI::error( 'Pantheon Sessions is currently disabled.' );
}

// Verify that the ID column/primary key does not already exist.
$query = "SHOW KEYS FROM {$table} WHERE key_name = 'PRIMARY';";
$key_existence = $wpdb->get_results( $query );

// Avoid errors by not attempting to add a column that already exists.
if ( ! empty( $key_existence ) ) {
WP_CLI::error( __( 'ID column already exists and does not need to be added to the table.', 'wp-native-php-sessions' ) );
}

// Alert the user that the action is going to go through.
WP_CLI::log( __( 'Primary Key does not exist, resolution starting.', 'wp-native-php-sessions' ) );

$count_query = "SELECT COUNT(*) FROM {$table};";
$count_total = $wpdb->get_results( $count_query );
$count_total = $count_total[0]->{'COUNT(*)'};

if ( $count_total >= 20000 ) {
WP_CLI::log( __( 'A total of ', 'wp-native-php-sessions' ) . $count_total . __( ' rows exist. To avoid service interruptions, this operation will be run in batches. Any sessions created between now and when operation completes may need to be recreated.', 'wp-native-php-sessions' ) );
}
// Create temporary table to copy data into in batches.
$query = "CREATE TABLE {$temp_clone_table} LIKE {$table};";
$wpdb->query( $query );
$query = "ALTER TABLE {$temp_clone_table} ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY FIRST";
$wpdb->query( $query );

$batch_size = 20000;
$loops = ceil( $count_total / $batch_size );

for ( $i = 0; $i < $loops; $i ++ ) {
$offset = $i * $batch_size;

$query = sprintf( "INSERT INTO {$temp_clone_table}
(user_id, session_id, secure_session_id, ip_address, datetime, data)
SELECT user_id,session_id,secure_session_id,ip_address,datetime,data
FROM %s ORDER BY user_id LIMIT %d OFFSET %d", $table, $batch_size, $offset );
$results = $wpdb->query( $query );
$current_results = $results + ( $batch_size * $i );

WP_CLI::log( __( 'Updated ', 'wp-native-php-sessions' ) . $current_results . ' / ' . $count_total . __( ' rows.', 'wp-native-php-sessions' ) );
}

// Hot swap the old table and the new table, deleting a previous old
// table if necessary.
$old_table = $wpdb->base_prefix . 'old_' . $unprefixed_table;
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $old_table ) );

if ( $wpdb->get_var( $query ) == $old_table ) {
$query = "DROP TABLE {$old_table};";
$wpdb->query( $query );
}
$query = "ALTER TABLE {$table} RENAME {$old_table};";
$wpdb->query( $query );
$query = "ALTER TABLE {$temp_clone_table} RENAME {$table};";
$wpdb->query( $query );

WP_CLI::log( __( 'Operation complete, please verify that your site is working as expected. When ready, run terminus wp {site_name}.{env} pantheon session primary-key-finalize to clean up old data, or run terminus wp {site_name}.{env} pantheon session primary-key-revert if there were issues.', 'wp-native-php-sessions' ) );
}

/**
* Finalizes the creation of a primary key by deleting the old data.
*
* @subcommand primary-key-finalize
*/
public function primary_key_finalize() {
global $wpdb;
$table = $wpdb->base_prefix . 'old_pantheon_sessions';

$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) );

// Check for table existence and delete if present.
if ( ! $wpdb->get_var( $query ) == $table ) {
WP_CLI::error( __( 'Old table does not exist to be removed.', 'wp-native-php-sessions' ) );
} else {
$query = "DROP TABLE {$table};";
$wpdb->query( $query );

WP_CLI::log( __( 'Old table has been successfully removed, process complete.', 'wp-native-php-sessions' ) );
}
}

/**
* Reverts addition of primary key.
*
* @subcommand primary-key-revert
*/
public function primary_key_revert() {
global $wpdb;
$old_clone_table = $wpdb->base_prefix . 'old_pantheon_sessions';
$temp_clone_table = $wpdb->base_prefix . 'temp_pantheon_sessions';
$table = $wpdb->base_prefix . 'pantheon_sessions';

// If there is no old table to roll back to, error.
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $old_clone_table ) );

if ( ! $wpdb->get_var( $query ) == $old_clone_table ) {
WP_CLI::error( __( 'There is no old table to roll back to.', 'wp-native-php-sessions' ) );
}

// Swap old table and new one.
$query = "ALTER TABLE {$table} RENAME {$temp_clone_table};";
$wpdb->query( $query );
$query = "ALTER TABLE {$old_clone_table} RENAME {$table};";
$wpdb->query( $query );
WP_CLI::log( __( 'Rolled back to previous state successfully, dropping corrupt table.', 'wp-native-php-sessions' ) );

// Remove table which did not function.
$query = "DROP TABLE {$temp_clone_table}";
$wpdb->query( $query );
WP_CLI::log( __( 'Process complete.', 'wp-native-php-sessions' ) );
}
}

\WP_CLI::add_command( 'pantheon session', '\Pantheon_Sessions\CLI_Command' );
46 changes: 46 additions & 0 deletions pantheon-sessions.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,51 @@ public static function force_first_load() {
}
}
}

/**
* Checks whether primary keys were set and notifies users if not.
*/
public static function check_native_primary_keys() {
global $wpdb;
$table_name = $wpdb->base_prefix . 'pantheon_sessions';
$old_table = $wpdb->base_prefix . 'old_pantheon_sessions';
$query = "SHOW KEYS FROM {$table_name} WHERE key_name = 'PRIMARY';";

$key_existence = $wpdb->get_results( $query );

if ( empty( $key_existence ) ) {
// If the key doesn't exist, recommend remediation.
?>
<div class="notice notice-error is-dismissible">
<p>
<?php
print __( 'Your PHP Native Sessions table is missing a primary key. Please run "wp {site_name}.dev pantheon session add-index" and verify that the process completes successfully and that this message goes away, then run "wp {site_name}.live pantheon session add-index" to resolve this issue on your live environment.',
'wp-native-php-sessions' );
?>
</p>
</div>
<?php
}

$query = $wpdb->prepare( 'SHOW TABLES LIKE %s',
$wpdb->esc_like( $old_table ) );

// Check for table existence and delete if present.
if ( $wpdb->get_var( $query ) == $old_table ) {
// If an old table exists but has not been removed, suggest doing so.
?>
<div class="notice notice-error">
<p>
<?php
print __( 'An old version of the PHP Native Sessions table is detected. When testing is complete, run wp {site_name}.{env} pantheon session primary-key-finalize to clean up old data, or run wp {site_name}.{env} pantheon session primary-key-revert if there were issues.',
'wp-native-php-sessions' );
?>
</p>
</div>
<?php
}

}
}

/**
Expand All @@ -273,5 +318,6 @@ function Pantheon_Sessions() {
}

add_action( 'activated_plugin', 'Pantheon_Sessions::force_first_load' );
add_action( 'admin_notices', 'Pantheon_Sessions::check_native_primary_keys' );

Pantheon_Sessions();

0 comments on commit 0a0b5a5

Please sign in to comment.