Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions lib/Cleantalk/ApbctWP/UpdatePlugin/DbColumnCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,242 @@ private function addColumnsIfNotExists()
$counter++;
}

// Update indexes
$this->updateIndexes($schema_table_structure[$table_key]['__indexes'], $db_column_names, $errors);

// Logging errors
if (!empty($errors)) {
DbAnalyzer::logSchemaErrors($errors, __FUNCTION__);
}
}

/**
* Update indexes based on schema definition
*
* @param mixed $schema_indexes_raw Index definitions from schema
* @param array $db_column_names Array of existing column names in the database
* @param array $errors Reference to errors array to append any errors
* @return void
*/
private function updateIndexes($schema_indexes_raw, $db_column_names, &$errors)
{
global $wpdb;

// Extract index definitions from schema
$schema_indexes = array();
$schema_index_names = array();
$db_indexes_raw = $wpdb->get_results("SHOW INDEX FROM `$this->dbTableName`", ARRAY_A);

if (is_string($schema_indexes_raw) && !empty($schema_indexes_raw)) {
// Parse index definitions from string like: "PRIMARY KEY (`id`), INDEX ( `network` , `mask` ), INDEX ( `status` )"
preg_match_all('/(?:PRIMARY\s+KEY|INDEX)\s*\([^)]+\)/i', $schema_indexes_raw, $matches);
if (isset($matches[0]) && !empty($matches[0])) {
foreach ($matches[0] as $match) {
// Extract column names from index definition
if (preg_match('/\(([^)]+)\)/', $match, $col_match) && isset($col_match[1])) {
$columns_raw = trim($col_match[1]);
$columns = preg_split('/\s*,\s*/', $columns_raw);
// Normalize column names
$normalized_columns = array();
foreach ($columns as $col) {
$normalized_columns[] = trim($col, '` ');
}

// Skip PRIMARY KEY as it should already exist if table has primary key
if (stripos($match, 'PRIMARY') !== false) {
continue;
}

if (!empty($normalized_columns)) {
$index_name = $normalized_columns[0];

$normalized_def = 'INDEX (' . implode(',', array_map(function ($col) {
return '`' . $col . '`';
}, $normalized_columns)) . ')';

// Use first column as index name
$schema_indexes[$index_name] = $normalized_def;
$schema_index_names[] = $index_name;
}
}
}
}
} elseif (is_array($schema_indexes_raw)) {
// If it's already an array, assume it's in format: index_name => definition
foreach ($schema_indexes_raw as $index_name => $index_def) {
if (is_string($index_name)) {
$schema_indexes[$index_name] = $index_def;
$schema_index_names[] = $index_name;
} else {
$schema_index_names[] = $index_def;
}
}
}

// Extract index names and definitions from DB results
$db_indexes = array(); // Index names
$db_index_definitions = array(); // Index name => array of columns (normalized)
if (is_array($db_indexes_raw)) {
foreach ($db_indexes_raw as $index_row) {
if (isset($index_row['Key_name'])) {
$key_name = $index_row['Key_name'];
if (!in_array($key_name, $db_indexes)) {
$db_indexes[] = $key_name;
}
// Build index definition: collect all columns for this index
if (!isset($db_index_definitions[$key_name])) {
$db_index_definitions[$key_name] = array();
}
// Store column name and sequence (order matters for composite indexes)
$column_name = isset($index_row['Column_name']) ? trim($index_row['Column_name'], '` ') : '';
$seq_in_index = isset($index_row['Seq_in_index']) ? (int)$index_row['Seq_in_index'] : 0;
if (!empty($column_name)) {
$db_index_definitions[$key_name][$seq_in_index] = $column_name;
}
}
}
// Normalize: sort by sequence and create comma-separated string for comparison
foreach ($db_index_definitions as $key_name => $columns) {
ksort($columns);
$db_index_definitions[$key_name] = implode(',', $columns);
}
}

// Build normalized schema index definitions for comparison
$schema_index_definitions = array();
foreach ($schema_indexes as $index_name => $index_def) {
// Extract columns from schema definition
if (preg_match('/\(([^)]+)\)/', $index_def, $col_match) && isset($col_match[1])) {
$columns = preg_split('/\s*,\s*/', trim($col_match[1], '` '));
// Normalize: trim and create comma-separated string
$normalized_columns = array();
foreach ($columns as $col) {
$normalized_columns[] = trim($col, '` ');
}
$schema_index_definitions[$index_name] = implode(',', $normalized_columns);
}
}

// Find indexes that need to be updated (exist in both but definitions differ)
$indexes_to_update = array();
foreach ($schema_index_names as $index_name) {
if (in_array($index_name, $db_indexes)) {
// Index exists in both - compare definitions
$schema_def = isset($schema_index_definitions[$index_name]) && is_string($schema_index_definitions[$index_name]) ? $schema_index_definitions[$index_name] : '';
$db_def = isset($db_index_definitions[$index_name]) && is_string($db_index_definitions[$index_name]) ? $db_index_definitions[$index_name] : '';
// Compare normalized definitions (case-insensitive)
if (strtolower($schema_def) !== strtolower($db_def)) {
$indexes_to_update[] = $index_name;
}
}
}

// Update indexes that have changed definitions
if (!empty($indexes_to_update)) {
foreach ($indexes_to_update as $index_name) {
// Skip PRIMARY KEY - it should be handled separately
if (strtoupper($index_name) === 'PRIMARY') {
continue;
}

// Drop the old index
$sql = "ALTER TABLE `$this->dbTableName` DROP INDEX `$index_name`";
$result = $wpdb->query($sql);
if ($result === false) {
$errors[] = "Failed to drop index `$index_name` for update.\nQuery: $wpdb->last_query\nError: $wpdb->last_error";
continue; // Skip recreation if drop failed
}

// Recreate with new definition
if (isset($schema_indexes[$index_name])) {
$index_def = $schema_indexes[$index_name];
if (preg_match('/\(([^)]+)\)/', $index_def, $col_match) && isset($col_match[1])) {
$columns = trim($col_match[1]);
$sql = "ALTER TABLE `$this->dbTableName` ADD INDEX `$index_name` ($columns)";
$result = $wpdb->query($sql);
if ($result === false) {
$errors[] = "Failed to recreate index `$index_name`.\nQuery: $wpdb->last_query\nError: $wpdb->last_error";
} else {
$this->dbTableChanged = true;
}
}
}
}
}

// Compare indexes - find indexes that are in schema but not in DB
$schema_index_names_lower = array_map('strtolower', $schema_index_names);
$db_indexes_lower = array_map('strtolower', $db_indexes);
$diff_indexes_lower = array_diff($schema_index_names_lower, $db_indexes_lower);

if (!empty($diff_indexes_lower)) {
// Map back to original case for schema lookup
$diff_indexes = array();
foreach ($diff_indexes_lower as $lower_name) {
$original_key = array_search($lower_name, $schema_index_names_lower);
if ($original_key !== false) {
$diff_indexes[] = $schema_index_names[$original_key];
}
}

// Add indexes to DB
foreach ($diff_indexes as $diff_index_name) {
// Get the full index definition from schema
if (isset($schema_indexes[$diff_index_name])) {
$index_def = $schema_indexes[$diff_index_name];
// Create proper SQL: ALTER TABLE `table` ADD INDEX `name` (`column`)
// Parse the index definition to extract columns
if (preg_match('/\(([^)]+)\)/', $index_def, $col_match) && isset($col_match[1])) {
$columns = trim($col_match[1]);
$sql = "ALTER TABLE `$this->dbTableName` ADD INDEX `$diff_index_name` ($columns)";
$result = $wpdb->query($sql);
if ($result === false) {
$errors[] = "Failed to add index `$diff_index_name`.\nQuery: $wpdb->last_query\nError: $wpdb->last_error";
} else {
$this->dbTableChanged = true;
}
} else {
$errors[] = "Could not parse index definition for `$diff_index_name`: $index_def";
}
} else {
// if we don't have the definition, try to create index on column with same name
if (in_array($diff_index_name, $db_column_names)) {
$sql = "ALTER TABLE `$this->dbTableName` ADD INDEX `$diff_index_name` (`$diff_index_name`)";
$result = $wpdb->query($sql);
if ($result === false) {
$errors[] = "Failed to add index `$diff_index_name`.\nQuery: $wpdb->last_query\nError: $wpdb->last_error";
} else {
$this->dbTableChanged = true;
}
} else {
$errors[] = "Index `$diff_index_name` not found in schema definitions and column doesn't exist for fallback creation.";
}
}
}
}

// Compare indexes - find indexes that are in DB but not in schema
$excess_indexes = array_diff($db_indexes, $schema_index_names);
if (!empty($excess_indexes)) {
// Remove indexes that don't exist in schema
foreach ($excess_indexes as $excess_index_name) {
// Skip PRIMARY KEY - it should be handled separately
if (strtoupper($excess_index_name) === 'PRIMARY') {
continue;
}

// Drop the index
$sql = "ALTER TABLE `$this->dbTableName` DROP INDEX `$excess_index_name`";
$result = $wpdb->query($sql);
if ($result === false) {
$errors[] = "Failed to drop index `$excess_index_name`.\nQuery: $wpdb->last_query\nError: $wpdb->last_error";
} else {
$this->dbTableChanged = true;
}
}
}
}

/**
* Get information about table changes
*/
Expand Down