Skip to content

Commit fef6787

Browse files
jamisonbryantdereuromark
authored andcommitted
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 743a893 commit fef6787

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
@@ -899,6 +899,20 @@ protected function getAddIndexInstructions(TableMetadata $table, Index $index):
899899
$this->getIndexSqlDefinition($index),
900900
);
901901

902+
// FULLTEXT indexes use post-steps (raw SQL) which executeAlterSteps
903+
// does not append algorithm/lock to, so we inline the clause here.
904+
// Setting on instructions as well ensures validation still runs.
905+
if ($index->getAlgorithm() !== null || $index->getLock() !== null) {
906+
if ($index->getAlgorithm() !== null) {
907+
$alter .= ', ALGORITHM=' . strtoupper($index->getAlgorithm());
908+
$instructions->setAlgorithm($index->getAlgorithm());
909+
}
910+
if ($index->getLock() !== null) {
911+
$alter .= ', LOCK=' . strtoupper($index->getLock());
912+
$instructions->setLock($index->getLock());
913+
}
914+
}
915+
902916
$instructions->addPostStep($alter);
903917
} else {
904918
$alter = sprintf(
@@ -907,6 +921,13 @@ protected function getAddIndexInstructions(TableMetadata $table, Index $index):
907921
);
908922

909923
$instructions->addAlter($alter);
924+
925+
if ($index->getAlgorithm() !== null) {
926+
$instructions->setAlgorithm($index->getAlgorithm());
927+
}
928+
if ($index->getLock() !== null) {
929+
$instructions->setLock($index->getLock());
930+
}
910931
}
911932

912933
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
@@ -3127,6 +3127,143 @@ public function testAlgorithmWithMixedCase(): void
31273127
$this->assertTrue($this->adapter->hasColumn('mixed_case', 'col2'));
31283128
}
31293129

3130+
public function testAddIndexWithAlgorithm(): void
3131+
{
3132+
$table = new Table('index_algo', [], $this->adapter);
3133+
$table->addColumn('email', 'string')
3134+
->create();
3135+
3136+
$table->addIndex('email', [
3137+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3138+
])->update();
3139+
3140+
$this->assertTrue($this->adapter->hasIndex('index_algo', ['email']));
3141+
}
3142+
3143+
public function testAddIndexWithAlgorithmAndLock(): void
3144+
{
3145+
$table = new Table('index_algo_lock', [], $this->adapter);
3146+
$table->addColumn('email', 'string')
3147+
->create();
3148+
3149+
$table->addIndex('email', [
3150+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3151+
'lock' => MysqlAdapter::LOCK_NONE,
3152+
])->update();
3153+
3154+
$this->assertTrue($this->adapter->hasIndex('index_algo_lock', ['email']));
3155+
}
3156+
3157+
public function testAddIndexWithAlgorithmCopy(): void
3158+
{
3159+
$table = new Table('index_copy', [], $this->adapter);
3160+
$table->addColumn('email', 'string')
3161+
->create();
3162+
3163+
$table->addIndex('email', [
3164+
'algorithm' => MysqlAdapter::ALGORITHM_COPY,
3165+
])->update();
3166+
3167+
$this->assertTrue($this->adapter->hasIndex('index_copy', ['email']));
3168+
}
3169+
3170+
public function testAddIndexWithAlgorithmMixedCase(): void
3171+
{
3172+
$table = new Table('index_case', [], $this->adapter);
3173+
$table->addColumn('email', 'string')
3174+
->create();
3175+
3176+
$table->addIndex('email', [
3177+
'algorithm' => 'inplace',
3178+
'lock' => 'none',
3179+
])->update();
3180+
3181+
$this->assertTrue($this->adapter->hasIndex('index_case', ['email']));
3182+
}
3183+
3184+
public function testAddIndexWithInvalidAlgorithmThrowsException(): void
3185+
{
3186+
$table = new Table('index_invalid_algo', [], $this->adapter);
3187+
$table->addColumn('email', 'string')
3188+
->create();
3189+
3190+
$this->expectException(InvalidArgumentException::class);
3191+
$this->expectExceptionMessage('Invalid algorithm');
3192+
3193+
$table->addIndex('email', [
3194+
'algorithm' => 'INVALID',
3195+
])->update();
3196+
}
3197+
3198+
public function testAddIndexWithInvalidLockThrowsException(): void
3199+
{
3200+
$table = new Table('index_invalid_lock', [], $this->adapter);
3201+
$table->addColumn('email', 'string')
3202+
->create();
3203+
3204+
$this->expectException(InvalidArgumentException::class);
3205+
$this->expectExceptionMessage('Invalid lock');
3206+
3207+
$table->addIndex('email', [
3208+
'lock' => 'INVALID',
3209+
])->update();
3210+
}
3211+
3212+
public function testAddIndexWithAlgorithmInstantAndExplicitLockThrowsException(): void
3213+
{
3214+
$table = new Table('index_instant_lock', [], $this->adapter);
3215+
$table->addColumn('email', 'string')
3216+
->create();
3217+
3218+
$this->expectException(InvalidArgumentException::class);
3219+
$this->expectExceptionMessage('ALGORITHM=INSTANT cannot be combined with LOCK=NONE');
3220+
3221+
$table->addIndex('email', [
3222+
'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
3223+
'lock' => MysqlAdapter::LOCK_NONE,
3224+
])->update();
3225+
}
3226+
3227+
public function testBatchedIndexesWithSameAlgorithm(): void
3228+
{
3229+
$table = new Table('index_batch', [], $this->adapter);
3230+
$table->addColumn('email', 'string')
3231+
->addColumn('name', 'string')
3232+
->create();
3233+
3234+
$table->addIndex('email', [
3235+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3236+
'lock' => MysqlAdapter::LOCK_NONE,
3237+
])
3238+
->addIndex('name', [
3239+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3240+
'lock' => MysqlAdapter::LOCK_NONE,
3241+
])
3242+
->update();
3243+
3244+
$this->assertTrue($this->adapter->hasIndex('index_batch', ['email']));
3245+
$this->assertTrue($this->adapter->hasIndex('index_batch', ['name']));
3246+
}
3247+
3248+
public function testBatchedIndexesWithConflictingAlgorithmsThrowsException()
3249+
{
3250+
$table = new Table('index_batch_conflict', [], $this->adapter);
3251+
$table->addColumn('email', 'string')
3252+
->addColumn('name', 'string')
3253+
->create();
3254+
3255+
$this->expectException(InvalidArgumentException::class);
3256+
$this->expectExceptionMessage('Conflicting algorithm specifications');
3257+
3258+
$table->addIndex('email', [
3259+
'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,
3260+
])
3261+
->addIndex('name', [
3262+
'algorithm' => MysqlAdapter::ALGORITHM_COPY,
3263+
])
3264+
->update();
3265+
}
3266+
31303267
public function testInsertOrUpdateWithDuplicates(): void
31313268
{
31323269
$table = new Table('currencies', [], $this->adapter);

0 commit comments

Comments
 (0)