Skip to content

Commit

Permalink
Fix recreating indices for renamed/removed columns.
Browse files Browse the repository at this point in the history
  • Loading branch information
ndm2 committed Dec 19, 2022
1 parent 73e94aa commit f7855f2
Show file tree
Hide file tree
Showing 2 changed files with 689 additions and 7 deletions.
219 changes: 212 additions & 7 deletions src/Phinx/Db/Adapter/SQLiteAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter
return $newState + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName);
}

/**
Expand Down Expand Up @@ -793,6 +793,178 @@ protected function getDeclaringIndexSql(string $tableName, string $indexName): s
return $sql;
}

/**
* Obtains index and trigger information for a table.
*
* They will be stored in the state as arrays under the `indices` and `triggers`
* keys accordingly.
*
* Index columns defined as expressions, as for example in `ON (ABS(id), other)`,
* will appear as `null`, so for the given example the columns for the index would
* look like `[null, 'other']`.
*
* @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify
* @param string $tableName The name of table being processed
* @return \Phinx\Db\Util\AlterInstructions
*/
protected function bufferIndicesAndTriggers(AlterInstructions $instructions, string $tableName): AlterInstructions
{
$instructions->addPostStep(function (array $state) use ($tableName): array {
$state['indices'] = [];
$state['triggers'] = [];

$rows = $this->fetchAll(
sprintf(
"
SELECT *
FROM sqlite_master
WHERE
(`type` = 'index' OR `type` = 'trigger')
AND tbl_name = %s
AND sql IS NOT NULL
",
$this->quoteValue($tableName)
)
);

$schema = $this->getSchemaName($tableName, true)['schema'];

foreach ($rows as $row) {
switch ($row['type']) {
case 'index':
$info = $this->fetchAll(
sprintf('PRAGMA %sindex_info(%s)', $schema, $this->quoteValue($row['name']))
);

$columns = array_map(
function ($column) {
if ($column === null) {
return null;
}

return strtolower($column);
},
array_column($info, 'name')
);
$hasExpressions = in_array(null, $columns, true);

$index = [
'columns' => $columns,
'hasExpressions' => $hasExpressions,
];

$state['indices'][] = $index + $row;
break;

case 'trigger':
$state['triggers'][] = $row;
break;
}
}

return $state;
});

return $instructions;
}

/**
* Filters out indices that reference a removed column.
*
* @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify
* @param string $columnName The name of the removed column
* @return \Phinx\Db\Util\AlterInstructions
*/
protected function filterIndicesForRemovedColumn(
AlterInstructions $instructions,
string $columnName
): AlterInstructions {
$instructions->addPostStep(function (array $state) use ($columnName): array {
foreach ($state['indices'] as $key => $index) {
if (
!$index['hasExpressions'] &&
in_array(strtolower($columnName), $index['columns'], true)
) {
unset($state['indices'][$key]);
}
}

return $state;
});

return $instructions;
}

/**
* Updates indices that reference a renamed column.
*
* @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify
* @param string $oldColumnName The old column name
* @param string $newColumnName The new column name
* @return \Phinx\Db\Util\AlterInstructions
*/
protected function updateIndicesForRenamedColumn(
AlterInstructions $instructions,
string $oldColumnName,
string $newColumnName
): AlterInstructions {
$instructions->addPostStep(function (array $state) use ($oldColumnName, $newColumnName): array {
foreach ($state['indices'] as $key => $index) {
if (
!$index['hasExpressions'] &&
in_array(strtolower($oldColumnName), $index['columns'], true)
) {
$pattern = '
/
(INDEX.+?ON\s.+?)
(\(\s*|,\s*) # opening parenthesis or comma
(?:`|"|\[)? # optional opening quote
(%s) # column name
(?:`|"|\])? # optional closing quote
(\s+COLLATE\s+.+?)? # optional collation
(\s+(?:ASC|DESC))? # optional order
(\s*,|\s*\)) # comma or closing parenthesis
/isx';

$newColumnName = $this->quoteColumnName($newColumnName);

$state['indices'][$key]['sql'] = preg_replace(
sprintf($pattern, preg_quote($oldColumnName, '/')),
"\\1\\2$newColumnName\\4\\5\\6",
$index['sql']
);
}
}

return $state;
});

return $instructions;
}

/**
* Recreates indices and triggers.
*
* @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to process
* @return \Phinx\Db\Util\AlterInstructions
*/
protected function recreateIndicesAndTriggers(AlterInstructions $instructions): AlterInstructions
{
$instructions->addPostStep(function (array $state): array {
foreach ($state['indices'] as $index) {
$this->execute($index['sql']);
}

foreach ($state['triggers'] as $trigger) {
$this->execute($trigger['sql']);
}

return $state;
});

return $instructions;
}

/**
* Copies all the data from a tmp table to another table
*
Expand Down Expand Up @@ -1016,6 +1188,39 @@ protected function beginAlterByCopyTable(string $tableName): AlterInstructions
return $instructions;
}

/**
* Returns the final instructions to alter a table using the
* create-copy-drop strategy.
*
* @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify
* @param string $tableName The name of table being processed
* @param ?string $renamedOrRemovedColumnName The name of the renamed or removed column when part of a column
* rename/drop operation.
* @param ?string $newColumnName The new column name when part of a column rename operation.
* @return \Phinx\Db\Util\AlterInstructions
*/
protected function endAlterByCopyTable(
AlterInstructions $instructions,
string $tableName,
?string $renamedOrRemovedColumnName = null,
?string $newColumnName = null
): AlterInstructions {
$instructions = $this->bufferIndicesAndTriggers($instructions, $tableName);

if ($renamedOrRemovedColumnName !== null) {
if ($newColumnName !== null) {
$this->updateIndicesForRenamedColumn($instructions, $renamedOrRemovedColumnName, $newColumnName);
} else {
$this->filterIndicesForRemovedColumn($instructions, $renamedOrRemovedColumnName);
}
}

$instructions = $this->copyAndDropTmpTable($instructions, $tableName);
$instructions = $this->recreateIndicesAndTriggers($instructions);

return $instructions;
}

/**
* @inheritDoc
*/
Expand All @@ -1040,7 +1245,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column
return $newState + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName, $columnName, $newColumnName);
}

/**
Expand Down Expand Up @@ -1069,7 +1274,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column
return $newState + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName);
}

/**
Expand Down Expand Up @@ -1101,7 +1306,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa
return $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName, $columnName);
}

/**
Expand Down Expand Up @@ -1377,7 +1582,7 @@ protected function getAddPrimaryKeyInstructions(Table $table, string $column): A
return compact('selectColumns', 'writeColumns') + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName);
}

/**
Expand Down Expand Up @@ -1459,7 +1664,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig
return compact('selectColumns', 'writeColumns') + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName);
}

/**
Expand Down Expand Up @@ -1517,7 +1722,7 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr
return $newState + $state;
});

return $this->copyAndDropTmpTable($instructions, $tableName);
return $this->endAlterByCopyTable($instructions, $tableName);
}

/**
Expand Down
Loading

0 comments on commit f7855f2

Please sign in to comment.