Skip to content

Commit 716ffa9

Browse files
committed
Add ALGORITHM/LOCK support to Index operations
Extend the ALGORITHM and LOCK clause support added in PR #955 for Column operations to also cover Index operations. This allows addIndex to pass algorithm and lock options through the fluent API on MySQL. Changes: - Add algorithm/lock properties, getters/setters to Index class - Wire algorithm/lock through getAddIndexInstructions in MysqlAdapter - Handle FULLTEXT indexes which use post-steps (inline the clause) - Add 9 tests mirroring the Column algorithm/lock test coverage
1 parent 4e108a3 commit 716ffa9

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

src/Db/Adapter/MysqlAdapter.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,20 @@ protected function getAddIndexInstructions(TableMetadata $table, Index $index):
854854
$this->getIndexSqlDefinition($index),
855855
);
856856

857+
// FULLTEXT indexes use post-steps (raw SQL) which executeAlterSteps
858+
// does not append algorithm/lock to, so we inline the clause here.
859+
// Setting on instructions as well ensures validation still runs.
860+
if ($index->getAlgorithm() !== null || $index->getLock() !== null) {
861+
if ($index->getAlgorithm() !== null) {
862+
$alter .= ', ALGORITHM=' . strtoupper($index->getAlgorithm());
863+
$instructions->setAlgorithm($index->getAlgorithm());
864+
}
865+
if ($index->getLock() !== null) {
866+
$alter .= ', LOCK=' . strtoupper($index->getLock());
867+
$instructions->setLock($index->getLock());
868+
}
869+
}
870+
857871
$instructions->addPostStep($alter);
858872
} else {
859873
$alter = sprintf(
@@ -862,6 +876,13 @@ protected function getAddIndexInstructions(TableMetadata $table, Index $index):
862876
);
863877

864878
$instructions->addAlter($alter);
879+
880+
if ($index->getAlgorithm() !== null) {
881+
$instructions->setAlgorithm($index->getAlgorithm());
882+
}
883+
if ($index->getLock() !== null) {
884+
$instructions->setLock($index->getLock());
885+
}
865886
}
866887

867888
return $instructions;

src/Db/Table/Index.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class Index extends DatabaseIndex
4747
* @param array<string>|null $include The included columns for covering indexes.
4848
* @param ?string $where The where clause for partial indexes.
4949
* @param bool $concurrent Whether to create the index concurrently.
50+
* @param ?string $algorithm The ALTER TABLE algorithm (MySQL-specific).
51+
* @param ?string $lock The ALTER TABLE lock mode (MySQL-specific).
5052
*/
5153
public function __construct(
5254
protected string $name = '',
@@ -57,6 +59,8 @@ public function __construct(
5759
protected ?array $include = null,
5860
protected ?string $where = null,
5961
protected bool $concurrent = false,
62+
protected ?string $algorithm = null,
63+
protected ?string $lock = null,
6064
) {
6165
}
6266

@@ -149,6 +153,52 @@ public function getConcurrently(): bool
149153
return $this->concurrent;
150154
}
151155

156+
/**
157+
* Sets the ALTER TABLE algorithm (MySQL-specific).
158+
*
159+
* @param string $algorithm Algorithm
160+
* @return $this
161+
*/
162+
public function setAlgorithm(string $algorithm)
163+
{
164+
$this->algorithm = $algorithm;
165+
166+
return $this;
167+
}
168+
169+
/**
170+
* Gets the ALTER TABLE algorithm.
171+
*
172+
* @return string|null
173+
*/
174+
public function getAlgorithm(): ?string
175+
{
176+
return $this->algorithm;
177+
}
178+
179+
/**
180+
* Sets the ALTER TABLE lock mode (MySQL-specific).
181+
*
182+
* @param string $lock Lock mode
183+
* @return $this
184+
*/
185+
public function setLock(string $lock)
186+
{
187+
$this->lock = $lock;
188+
189+
return $this;
190+
}
191+
192+
/**
193+
* Gets the ALTER TABLE lock mode.
194+
*
195+
* @return string|null
196+
*/
197+
public function getLock(): ?string
198+
{
199+
return $this->lock;
200+
}
201+
152202
/**
153203
* Utility method that maps an array of index options to this object's methods.
154204
*
@@ -159,7 +209,7 @@ public function getConcurrently(): bool
159209
public function setOptions(array $options)
160210
{
161211
// Valid Options
162-
$validOptions = ['concurrently', 'type', 'unique', 'name', 'limit', 'order', 'include', 'where'];
212+
$validOptions = ['concurrently', 'type', 'unique', 'name', 'limit', 'order', 'include', 'where', 'algorithm', 'lock'];
163213
foreach ($options as $option => $value) {
164214
if (!in_array($option, $validOptions, true)) {
165215
throw new RuntimeException(sprintf('"%s" is not a valid index option.', $option));

tests/TestCase/Db/Adapter/MysqlAdapterTest.php

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3037,6 +3037,143 @@ public function testAlgorithmWithMixedCase()
30373037
$this->assertTrue($this->adapter->hasColumn('mixed_case', 'col2'));
30383038
}
30393039

3040+
public function testAddIndexWithAlgorithm()
3041+
{
3042+
$table = new Table('index_algo', [], $this->adapter);
3043+
$table->addColumn('email', 'string')
3044+
->create();
3045+
3046+
$table->addIndex('email', [
3047+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3048+
])->update();
3049+
3050+
$this->assertTrue($this->adapter->hasIndex('index_algo', ['email']));
3051+
}
3052+
3053+
public function testAddIndexWithAlgorithmAndLock()
3054+
{
3055+
$table = new Table('index_algo_lock', [], $this->adapter);
3056+
$table->addColumn('email', 'string')
3057+
->create();
3058+
3059+
$table->addIndex('email', [
3060+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3061+
'lock' => MysqlAdapter::LOCK_NONE,
3062+
])->update();
3063+
3064+
$this->assertTrue($this->adapter->hasIndex('index_algo_lock', ['email']));
3065+
}
3066+
3067+
public function testAddIndexWithAlgorithmCopy()
3068+
{
3069+
$table = new Table('index_copy', [], $this->adapter);
3070+
$table->addColumn('email', 'string')
3071+
->create();
3072+
3073+
$table->addIndex('email', [
3074+
'algorithm' => MysqlAdapter::ALGORITHM_COPY,
3075+
])->update();
3076+
3077+
$this->assertTrue($this->adapter->hasIndex('index_copy', ['email']));
3078+
}
3079+
3080+
public function testAddIndexWithAlgorithmMixedCase()
3081+
{
3082+
$table = new Table('index_case', [], $this->adapter);
3083+
$table->addColumn('email', 'string')
3084+
->create();
3085+
3086+
$table->addIndex('email', [
3087+
'algorithm' => 'inplace',
3088+
'lock' => 'none',
3089+
])->update();
3090+
3091+
$this->assertTrue($this->adapter->hasIndex('index_case', ['email']));
3092+
}
3093+
3094+
public function testAddIndexWithInvalidAlgorithmThrowsException()
3095+
{
3096+
$table = new Table('index_invalid_algo', [], $this->adapter);
3097+
$table->addColumn('email', 'string')
3098+
->create();
3099+
3100+
$this->expectException(InvalidArgumentException::class);
3101+
$this->expectExceptionMessage('Invalid algorithm');
3102+
3103+
$table->addIndex('email', [
3104+
'algorithm' => 'INVALID',
3105+
])->update();
3106+
}
3107+
3108+
public function testAddIndexWithInvalidLockThrowsException()
3109+
{
3110+
$table = new Table('index_invalid_lock', [], $this->adapter);
3111+
$table->addColumn('email', 'string')
3112+
->create();
3113+
3114+
$this->expectException(InvalidArgumentException::class);
3115+
$this->expectExceptionMessage('Invalid lock');
3116+
3117+
$table->addIndex('email', [
3118+
'lock' => 'INVALID',
3119+
])->update();
3120+
}
3121+
3122+
public function testAddIndexWithAlgorithmInstantAndExplicitLockThrowsException()
3123+
{
3124+
$table = new Table('index_instant_lock', [], $this->adapter);
3125+
$table->addColumn('email', 'string')
3126+
->create();
3127+
3128+
$this->expectException(InvalidArgumentException::class);
3129+
$this->expectExceptionMessage('ALGORITHM=INSTANT cannot be combined with LOCK=NONE');
3130+
3131+
$table->addIndex('email', [
3132+
'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
3133+
'lock' => MysqlAdapter::LOCK_NONE,
3134+
])->update();
3135+
}
3136+
3137+
public function testBatchedIndexesWithSameAlgorithm()
3138+
{
3139+
$table = new Table('index_batch', [], $this->adapter);
3140+
$table->addColumn('email', 'string')
3141+
->addColumn('name', 'string')
3142+
->create();
3143+
3144+
$table->addIndex('email', [
3145+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3146+
'lock' => MysqlAdapter::LOCK_NONE,
3147+
])
3148+
->addIndex('name', [
3149+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3150+
'lock' => MysqlAdapter::LOCK_NONE,
3151+
])
3152+
->update();
3153+
3154+
$this->assertTrue($this->adapter->hasIndex('index_batch', ['email']));
3155+
$this->assertTrue($this->adapter->hasIndex('index_batch', ['name']));
3156+
}
3157+
3158+
public function testBatchedIndexesWithConflictingAlgorithmsThrowsException()
3159+
{
3160+
$table = new Table('index_batch_conflict', [], $this->adapter);
3161+
$table->addColumn('email', 'string')
3162+
->addColumn('name', 'string')
3163+
->create();
3164+
3165+
$this->expectException(InvalidArgumentException::class);
3166+
$this->expectExceptionMessage('Conflicting algorithm specifications');
3167+
3168+
$table->addIndex('email', [
3169+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3170+
])
3171+
->addIndex('name', [
3172+
'algorithm' => MysqlAdapter::ALGORITHM_COPY,
3173+
])
3174+
->update();
3175+
}
3176+
30403177
public function testInsertOrUpdateWithDuplicates()
30413178
{
30423179
$table = new Table('currencies', [], $this->adapter);

0 commit comments

Comments
 (0)