Skip to content

Resolve: Create migration for column position change if a field position is changed in spec #58 #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d4d01f5
Create PR
SOHELAHMED7 Sep 28, 2024
290b7be
Add a failing test
SOHELAHMED7 Sep 28, 2024
3718dc6
Cleanup
SOHELAHMED7 Sep 28, 2024
79bc270
Fix issue: case: drop 2 column: its position in add column in down co…
SOHELAHMED7 Oct 3, 2024
d5c2c28
Handle column position in case of dropping a column (in down code -> …
SOHELAHMED7 Oct 4, 2024
1516b12
Fix style
SOHELAHMED7 Oct 4, 2024
3a71619
Add more tests
SOHELAHMED7 Oct 4, 2024
dae990b
Add more tests 2
SOHELAHMED7 Oct 4, 2024
4f3efe9
Refactor
SOHELAHMED7 Oct 5, 2024
53bbf08
Implementation in progress + add more tests
SOHELAHMED7 Oct 6, 2024
832c102
Implement for many test cases
SOHELAHMED7 Oct 7, 2024
b58bf2e
Resolve TODOs + add tests
SOHELAHMED7 Oct 7, 2024
6fa477f
Fix bugs + fix failing tests + add more tests
SOHELAHMED7 Oct 7, 2024
a0090f7
Fix bugs
SOHELAHMED7 Oct 8, 2024
3dcc87e
Refactor
SOHELAHMED7 Oct 8, 2024
9ef528c
Fix bugs + fix failing tests + add more tests + refactor
SOHELAHMED7 Oct 8, 2024
9310dc6
Fix failing tests
SOHELAHMED7 Oct 10, 2024
617fdf5
Fix other failing tests
SOHELAHMED7 Oct 10, 2024
85aac91
Fix bugs + fix failing tests
SOHELAHMED7 Oct 11, 2024
95db88f
Fix bugs + fix failing tests 2
SOHELAHMED7 Oct 12, 2024
84c9655
Change tests and add few more
SOHELAHMED7 Oct 12, 2024
e54defe
Fix bug
SOHELAHMED7 Oct 14, 2024
ca22731
Add docs + refactor
SOHELAHMED7 Oct 14, 2024
815907e
Refactor
SOHELAHMED7 Oct 14, 2024
edbd140
Add more tests + refactor `setColumnsPositions()` to remove unwanted …
SOHELAHMED7 Oct 15, 2024
7cb8ab8
Modify variable name
SOHELAHMED7 Oct 15, 2024
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
20 changes: 20 additions & 0 deletions src/db/ColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,24 @@ class ColumnSchema extends \yii\db\ColumnSchema
* ```
*/
public $xDbType;

/**
* Used only for MySQL/MariaDB
* @var array|null
* [
* index => int # position: starts from 1
* after => ?string # after column
* before => ?string # before column
* ]
* If `before` is null then column is last
* If `after` is null then column is first
* If both are null then table has only 1 column
*/
public ?array $fromPosition = null;
public ?array $toPosition = null;

/**
* From `$this->fromPosition` and `$this->toPosition` we can check if the position is changed or not. This is done in `BaseMigrationBuilder::setColumnsPositions()`
*/
public bool $isPositionChanged = false;
}
8 changes: 4 additions & 4 deletions src/generator/default/dbmodel.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
*/
abstract class <?= $model->getClassName() ?> extends \yii\db\ActiveRecord
{
<?php if($scenarios = $model->getScenarios()):
foreach($scenarios as $scenario): ?>
<?php if ($scenarios = $model->getScenarios()):
foreach ($scenarios as $scenario): ?>
/**
*<?= $scenario['description'] ?>

Expand All @@ -76,7 +76,7 @@ public static function tableName()
{
return <?= var_export($model->getTableAlias()) ?>;
}
<?php if($scenarios): ?>
<?php if ($scenarios): ?>

/**
* Automatically generated scenarios from the model 'x-scenarios'.
Expand All @@ -92,7 +92,7 @@ public function scenarios()
$default = parent::scenarios()[self::SCENARIO_DEFAULT];

return [
<?php foreach($scenarios as $scenario): ?>
<?php foreach ($scenarios as $scenario): ?>
self::<?= $scenario['const'] ?> => $default,
<?php endforeach; ?>
/**
Expand Down
62 changes: 19 additions & 43 deletions src/lib/migrations/BaseMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public function buildSecondary(?ManyToManyRelation $relation = null):MigrationMo
{
$this->migration = Yii::createObject(MigrationModel::class, [$this->model, false, $relation, []]);
$this->newColumns = $relation->columnSchema ?? $this->model->attributesToColumnSchema();
$this->setColumnsPositions();
$wantNames = array_keys($this->newColumns);
$haveNames = $this->tableSchema->columnNames;
$columnsForCreate = array_map(
Expand All @@ -184,7 +185,7 @@ function (string $missingColumn) {
function (string $unknownColumn) {
return $this->tableSchema->columns[$unknownColumn];
},
array_diff($haveNames, $wantNames)
array_reverse(array_diff($haveNames, $wantNames), true)
);

$columnsForChange = array_intersect($wantNames, $haveNames);
Expand Down Expand Up @@ -220,6 +221,7 @@ function (string $unknownColumn) {
} else {
$this->buildRelations();
}

return $this->migration;
}

Expand Down Expand Up @@ -249,12 +251,12 @@ protected function buildColumnsDrop(array $columns):void
{
foreach ($columns as $column) {
$tableName = $this->model->getTableAlias();
$position = $this->findPosition($column, true);
if ($column->isPrimaryKey && !$column->autoIncrement) {
$pkName = 'pk_' . $this->model->tableName . '_' . $column->name;
$this->migration->addDownCode($this->recordBuilder->addPrimaryKey($tableName, [$column->name], $pkName))
->addUpCode($this->recordBuilder->dropPrimaryKey($tableName, [$column->name], $pkName));
}
$position = $this->findPosition($column, true);
$this->migration->addDownCode($this->recordBuilder->addDbColumn($tableName, $column, $position))
->addUpCode($this->recordBuilder->dropColumn($tableName, $column->name));
}
Expand Down Expand Up @@ -513,47 +515,6 @@ public function isDefaultValueChanged(
return false;
}

/**
* Given a column, compute its previous column name present in OpenAPI schema
* @return ?string
* `null` if column is added at last
* 'FIRST' if column is added at first position
* 'AFTER <columnName>' if column is added in between e.g. if 'email' is added after 'username' then 'AFTER username'
*/
public function findPosition(ColumnSchema $column, bool $forDrop = false): ?string
{
$columnNames = array_keys($forDrop ? $this->tableSchema->columns : $this->newColumns);

$key = array_search($column->name, $columnNames);
if ($key > 0) {
$prevColName = $columnNames[$key-1];

if (!isset($columnNames[$key+1])) { // if new col is added at last then no need to add 'AFTER' SQL part. This is checked as if next column is present or not
return null;
}

// in case of `down()` code of migration, putting 'after <colName>' in add column statmenet is erroneous because <colName> may not exist.
// Example: From col a, b, c, d, if I drop c and d then their migration code will be generated like:
// `up()` code
// drop c
// drop d
// `down()` code
// add d after c (c does not exist! Error!)
// add c
if ($forDrop) {
return null;
}


return self::POS_AFTER . ' ' . $prevColName;

// if no `$columnSchema` is found, previous column does not exist. This happens when 'after column' is not yet added in migration or added after currently undertaken column
} elseif ($key === 0) {
return self::POS_FIRST;
}

return null;
}

public function modifyDesiredFromDbInContextOfDesired(ColumnSchema $desired, ColumnSchema $desiredFromDb): void
{
Expand All @@ -574,4 +535,19 @@ public function modifyDesiredInContextOfDesiredFromDb(ColumnSchema $desired, Col
}
$desired->dbType = $desiredFromDb->dbType;
}

/**
* Only for MySQL and MariaDB
* Given a column, compute its previous column name present in OpenAPI schema
* @param ColumnSchema $column
* @param bool $forDrop
* @param bool $forAlter
* @return ?string
* `null` if column is added at last
* 'FIRST' if column is added at first position
* 'AFTER <columnName>' if column is added in between e.g. if 'email' is added after 'username' then 'AFTER username'
*/
abstract public function findPosition(ColumnSchema $column, bool $forDrop = false, bool $forAlter = false): ?string;

abstract public function setColumnsPositions();
}
30 changes: 19 additions & 11 deletions src/lib/migrations/MigrationRecordBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,55 +112,63 @@ public function addDbColumn(string $tableAlias, ColumnSchema $column, ?string $p
/**
* @throws \yii\base\InvalidConfigException
*/
public function alterColumn(string $tableAlias, ColumnSchema $column):string
public function alterColumn(string $tableAlias, ColumnSchema $column, ?string $position = null):string
{
if (property_exists($column, 'xDbType') && is_string($column->xDbType) && !empty($column->xDbType)) {
$converter = $this->columnToCode($tableAlias, $column, true, false, true, true);
$converter = $this->columnToCode($tableAlias, $column, true, false, true, true, $position);
return sprintf(
ApiGenerator::isPostgres() ? self::ALTER_COLUMN_RAW_PGSQL : self::ALTER_COLUMN_RAW,
$tableAlias,
$column->name,
ColumnToCode::escapeQuotes($converter->getCode())
);
}
$converter = $this->columnToCode($tableAlias, $column, true);
$converter = $this->columnToCode($tableAlias, $column, true, false, false, false, $position);
return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getCode(true));
}

/**
* @throws \yii\base\InvalidConfigException
*/
public function alterColumnType(string $tableAlias, ColumnSchema $column, bool $addUsing = false):string
{
public function alterColumnType(
string $tableAlias,
ColumnSchema $column,
bool $addUsing = false,
?string $position = null
):string {
if (property_exists($column, 'xDbType') && is_string($column->xDbType) && !empty($column->xDbType)) {
$converter = $this->columnToCode($tableAlias, $column, false, false, true, true);
$converter = $this->columnToCode($tableAlias, $column, false, false, true, true, $position);
return sprintf(
ApiGenerator::isPostgres() ? self::ALTER_COLUMN_RAW_PGSQL : self::ALTER_COLUMN_RAW,
$tableAlias,
$column->name,
rtrim(ltrim($converter->getAlterExpression($addUsing), "'"), "'")
);
}
$converter = $this->columnToCode($tableAlias, $column, false);
$converter = $this->columnToCode($tableAlias, $column, false, false, false, false, $position);
return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getAlterExpression($addUsing));
}

/**
* This method is only used in Pgsql
* @throws \yii\base\InvalidConfigException
*/
public function alterColumnTypeFromDb(string $tableAlias, ColumnSchema $column, bool $addUsing = false) :string
{
public function alterColumnTypeFromDb(
string $tableAlias,
ColumnSchema $column,
bool $addUsing = false,
?string $position = null
) :string {
if (property_exists($column, 'xDbType') && is_string($column->xDbType) && !empty($column->xDbType)) {
$converter = $this->columnToCode($tableAlias, $column, true, false, true, true);
$converter = $this->columnToCode($tableAlias, $column, true, false, true, true, $position);
return sprintf(
ApiGenerator::isPostgres() ? self::ALTER_COLUMN_RAW_PGSQL : self::ALTER_COLUMN_RAW,
$tableAlias,
$column->name,
rtrim(ltrim($converter->getAlterExpression($addUsing), "'"), "'")
);
}
$converter = $this->columnToCode($tableAlias, $column, true);
$converter = $this->columnToCode($tableAlias, $column, true, false, false, false, $position);
return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getAlterExpression($addUsing));
}

Expand Down
Loading
Loading