Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
180 changes: 180 additions & 0 deletions docs/en/writing-migrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,186 @@ configuration key for the time being.

To view available column types and options, see :ref:`adding-columns` for details.

Table Partitioning
------------------

Migrations supports table partitioning for MySQL and PostgreSQL. Partitioning helps
manage large tables by splitting them into smaller, more manageable pieces.

.. note::

Partition columns must be included in the primary key for MySQL. SQLite does
not support partitioning. MySQL's ``RANGE`` and ``LIST`` types only work with
integer columns - use ``RANGE COLUMNS`` and ``LIST COLUMNS`` for DATE/STRING columns.

RANGE Partitioning
~~~~~~~~~~~~~~~~~~

RANGE partitioning is useful when you want to partition by numeric ranges. For MySQL,
use ``TYPE_RANGE`` with integer columns or expressions, and ``TYPE_RANGE_COLUMNS`` for
DATE/DATETIME/STRING columns::

<?php

use Migrations\BaseMigration;
use Migrations\Db\Table\Partition;

class CreatePartitionedOrders extends BaseMigration
{
public function change(): void
{
// Use RANGE COLUMNS for DATE columns in MySQL
$table = $this->table('orders', [
'id' => false,
'primary_key' => ['id', 'order_date'],
]);
$table->addColumn('id', 'integer', ['identity' => true])
->addColumn('order_date', 'date')
->addColumn('amount', 'decimal', ['precision' => 10, 'scale' => 2])
->partitionBy(Partition::TYPE_RANGE_COLUMNS, 'order_date')
->addPartition('p2022', '2023-01-01')
->addPartition('p2023', '2024-01-01')
->addPartition('p2024', '2025-01-01')
->addPartition('pmax', 'MAXVALUE')
->create();
}
}

LIST Partitioning
~~~~~~~~~~~~~~~~~

LIST partitioning is useful when you want to partition by discrete values. For MySQL,
use ``TYPE_LIST`` with integer columns and ``TYPE_LIST_COLUMNS`` for STRING columns::

<?php

use Migrations\BaseMigration;
use Migrations\Db\Table\Partition;

class CreatePartitionedCustomers extends BaseMigration
{
public function change(): void
{
// Use LIST COLUMNS for STRING columns in MySQL
$table = $this->table('customers', [
'id' => false,
'primary_key' => ['id', 'region'],
]);
$table->addColumn('id', 'integer', ['identity' => true])
->addColumn('region', 'string', ['limit' => 20])
->addColumn('name', 'string')
->partitionBy(Partition::TYPE_LIST_COLUMNS, 'region')
->addPartition('p_americas', ['US', 'CA', 'MX', 'BR'])
->addPartition('p_europe', ['UK', 'DE', 'FR', 'IT'])
->addPartition('p_asia', ['JP', 'CN', 'IN', 'KR'])
->create();
}
}

HASH Partitioning
~~~~~~~~~~~~~~~~~

HASH partitioning distributes data evenly across a specified number of partitions::

<?php

use Migrations\BaseMigration;
use Migrations\Db\Table\Partition;

class CreatePartitionedSessions extends BaseMigration
{
public function change(): void
{
$table = $this->table('sessions');
$table->addColumn('user_id', 'integer')
->addColumn('data', 'text')
->partitionBy(Partition::TYPE_HASH, 'user_id', ['count' => 8])
->create();
}
}

KEY Partitioning (MySQL only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

KEY partitioning is similar to HASH but uses MySQL's internal hashing function::

<?php

use Migrations\BaseMigration;
use Migrations\Db\Table\Partition;

class CreatePartitionedCache extends BaseMigration
{
public function change(): void
{
$table = $this->table('cache', [
'id' => false,
'primary_key' => ['cache_key'],
]);
$table->addColumn('cache_key', 'string', ['limit' => 255])
->addColumn('value', 'binary')
->partitionBy(Partition::TYPE_KEY, 'cache_key', ['count' => 16])
->create();
}
}

Partitioning with Expressions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can partition by expressions using the ``Literal`` class::

<?php

use Migrations\BaseMigration;
use Migrations\Db\Literal;
use Migrations\Db\Table\Partition;

class CreatePartitionedEvents extends BaseMigration
{
public function change(): void
{
$table = $this->table('events', [
'id' => false,
'primary_key' => ['id', 'created_at'],
]);
$table->addColumn('id', 'integer', ['identity' => true])
->addColumn('created_at', 'datetime')
->partitionBy(Partition::TYPE_RANGE, Literal::from('YEAR(created_at)'))
->addPartition('p2022', 2023)
->addPartition('p2023', 2024)
->addPartition('pmax', 'MAXVALUE')
->create();
}
}

Modifying Partitions on Existing Tables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can add or drop partitions on existing partitioned tables::

<?php

use Migrations\BaseMigration;

class ModifyOrdersPartitions extends BaseMigration
{
public function up(): void
{
// Add a new partition
$this->table('orders')
->addPartitionToExisting('p2025', '2026-01-01')
->update();
}

public function down(): void
{
// Drop the partition
$this->table('orders')
->dropPartition('p2025')
->update();
}
}

Saving Changes
--------------

Expand Down
45 changes: 45 additions & 0 deletions src/Db/Action/AddPartition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Migrations\Db\Action;

use Migrations\Db\Table\PartitionDefinition;
use Migrations\Db\Table\TableMetadata;

/**
* Add a partition to an existing partitioned table
*/
class AddPartition extends Action
{
/**
* @var \Migrations\Db\Table\PartitionDefinition
*/
protected PartitionDefinition $partition;

/**
* Constructor
*
* @param \Migrations\Db\Table\TableMetadata $table The table to add the partition to
* @param \Migrations\Db\Table\PartitionDefinition $partition The partition definition
*/
public function __construct(TableMetadata $table, PartitionDefinition $partition)
{
parent::__construct($table);
$this->partition = $partition;
}

/**
* Returns the partition definition to add
*
* @return \Migrations\Db\Table\PartitionDefinition
*/
public function getPartition(): PartitionDefinition
{
return $this->partition;
}
}
44 changes: 44 additions & 0 deletions src/Db/Action/DropPartition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Migrations\Db\Action;

use Migrations\Db\Table\TableMetadata;

/**
* Drop a partition from an existing partitioned table
*/
class DropPartition extends Action
{
/**
* @var string
*/
protected string $partitionName;

/**
* Constructor
*
* @param \Migrations\Db\Table\TableMetadata $table The table to drop the partition from
* @param string $partitionName The name of the partition to drop
*/
public function __construct(TableMetadata $table, string $partitionName)
{
parent::__construct($table);
$this->partitionName = $partitionName;
}

/**
* Returns the partition name to drop
*
* @return string
*/
public function getPartitionName(): string
{
return $this->partitionName;
}
}
45 changes: 45 additions & 0 deletions src/Db/Adapter/AbstractAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
use Migrations\Db\Action\AddColumn;
use Migrations\Db\Action\AddForeignKey;
use Migrations\Db\Action\AddIndex;
use Migrations\Db\Action\AddPartition;
use Migrations\Db\Action\ChangeColumn;
use Migrations\Db\Action\ChangeComment;
use Migrations\Db\Action\ChangePrimaryKey;
use Migrations\Db\Action\DropForeignKey;
use Migrations\Db\Action\DropIndex;
use Migrations\Db\Action\DropPartition;
use Migrations\Db\Action\DropTable;
use Migrations\Db\Action\RemoveColumn;
use Migrations\Db\Action\RenameColumn;
Expand All @@ -43,6 +45,7 @@
use Migrations\Db\Table\Column;
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use Migrations\Db\Table\PartitionDefinition;
use Migrations\Db\Table\TableMetadata;
use Migrations\MigrationInterface;
use Migrations\SeedInterface;
Expand Down Expand Up @@ -1423,6 +1426,32 @@ public function dropCheckConstraint(string $tableName, string $constraintName):
*/
abstract protected function getDropCheckConstraintInstructions(string $tableName, string $constraintName): AlterInstructions;

/**
* Returns the instructions to add a partition to an existing partitioned table.
*
* @param \Migrations\Db\Table\TableMetadata $table The table
* @param \Migrations\Db\Table\PartitionDefinition $partition The partition definition to add
* @throws \RuntimeException If partitioning is not supported
* @return \Migrations\Db\AlterInstructions
*/
protected function getAddPartitionInstructions(TableMetadata $table, PartitionDefinition $partition): AlterInstructions
{
throw new RuntimeException('Table partitioning is not supported by this adapter');
}

/**
* Returns the instructions to drop a partition from an existing partitioned table.
*
* @param string $tableName The table name
* @param string $partitionName The partition name to drop
* @throws \RuntimeException If partitioning is not supported
* @return \Migrations\Db\AlterInstructions
*/
protected function getDropPartitionInstructions(string $tableName, string $partitionName): AlterInstructions
{
throw new RuntimeException('Table partitioning is not supported by this adapter');
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -1610,6 +1639,22 @@ public function executeActions(TableMetadata $table, array $actions): void
));
break;

case $action instanceof AddPartition:
/** @var \Migrations\Db\Action\AddPartition $action */
$instructions->merge($this->getAddPartitionInstructions(
$table,
$action->getPartition(),
));
break;

case $action instanceof DropPartition:
/** @var \Migrations\Db\Action\DropPartition $action */
$instructions->merge($this->getDropPartitionInstructions(
$table->getName(),
$action->getPartitionName(),
));
break;

default:
throw new InvalidArgumentException(
sprintf("Don't know how to execute action `%s`", get_class($action)),
Expand Down
Loading