From 01057cd09c1db1fc08d76b4385f6e2524b0d4711 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 11 Nov 2026 18:32:58 +0530 Subject: [PATCH 1/4] fix(attributes): drop index lengths and orders with a deleted attribute Deleting an attribute removed it from each index's attributes but kept its length and order, so the index ended up with more lengths than attributes and every later updateAttribute() failed index validation. --- src/Database/Trait/Attributes.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Database/Trait/Attributes.php b/src/Database/Trait/Attributes.php index 1300dd4a3..9c61a7576 100644 --- a/src/Database/Trait/Attributes.php +++ b/src/Database/Trait/Attributes.php @@ -915,13 +915,21 @@ private static function withoutIndexedAttribute(array $indexes, string $key): ar continue; } - $attributes = \array_values(\array_filter( - $index->attributes, - static fn (string $attribute): bool => $attribute !== $key, - )); + // Lengths and orders are positional, so drop the entry at the removed attribute's position too. + $position = \array_search($key, $index->attributes, true); + $document = $index->toDocument(); + $attributes = $index->attributes; + $lengths = $document->getAttribute('lengths', []); + $orders = $document->getAttribute('orders', []); + \array_splice($attributes, $position, 1); + \array_splice($lengths, $position, 1); + \array_splice($orders, $position, 1); if ($attributes !== []) { - $remaining[] = self::withIndexedAttributes($index, $attributes); + $remaining[] = Index::fromDocument($document + ->setAttribute(self::INDEX_ATTRIBUTES, $attributes) + ->setAttribute('lengths', $lengths) + ->setAttribute('orders', $orders)); } } From 2a3017e377d66cdb1d9fa740c32f86c9f00e3eb9 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 11 Nov 2026 18:36:15 +0530 Subject: [PATCH 2/4] test(indexes): cover index lengths after deleting one of its attributes --- tests/e2e/Adapter/Scopes/IndexTests.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index 1abe440ff..458778830 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -137,6 +137,29 @@ public function testCreateDeleteIndex(): void $database->deleteCollection('indexes'); } + public function testDeleteAttributeKeepsIndexLengthsAligned(): void + { + /** @var Database $database */ + $database = $this->getDatabase(); + + $database->createCollection(Collection::create(id: 'indexLengths', attributes: [ + Attribute::string(key: 'first', size: 128), + Attribute::string(key: 'second', size: 128), + ], indexes: [ + Index::key(key: 'pair', attributes: ['first', 'second'], lengths: [64, 32], orders: [OrderDirection::Asc, OrderDirection::Desc]), + ])); + + $database->deleteAttribute('indexLengths', 'first'); + + $index = $database->getCollection('indexLengths')->indexes()[0]; + $this->assertSame([32], $index->lengths); + $this->assertSame([OrderDirection::Desc], $index->orders); + + $database->updateAttribute('indexLengths', 'second', new Update(size: 256)); + + $database->deleteCollection('indexLengths'); + } + public function testCreateUniqueIndexOverDuplicates(): void { $database = $this->getDatabase(); From af9f43633344b76adb29a7355da4fa189b2da884 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 11 Nov 2026 18:56:33 +0530 Subject: [PATCH 3/4] fix(attributes): satisfy PHPStan types in index pruning --- src/Database/Trait/Attributes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Trait/Attributes.php b/src/Database/Trait/Attributes.php index 9c61a7576..88a53d291 100644 --- a/src/Database/Trait/Attributes.php +++ b/src/Database/Trait/Attributes.php @@ -916,11 +916,11 @@ private static function withoutIndexedAttribute(array $indexes, string $key): ar } // Lengths and orders are positional, so drop the entry at the removed attribute's position too. - $position = \array_search($key, $index->attributes, true); + $position = (int) \array_search($key, $index->attributes, true); $document = $index->toDocument(); $attributes = $index->attributes; - $lengths = $document->getAttribute('lengths', []); - $orders = $document->getAttribute('orders', []); + $lengths = (array) $document->getAttribute('lengths', []); + $orders = (array) $document->getAttribute('orders', []); \array_splice($attributes, $position, 1); \array_splice($lengths, $position, 1); \array_splice($orders, $position, 1); From 6265234ceeed51262c489cd21acdac9c23fbc6d8 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 11 Nov 2026 19:04:07 +0530 Subject: [PATCH 4/4] fix(attributes): prune every position of a deleted attribute from an index --- src/Database/Trait/Attributes.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Database/Trait/Attributes.php b/src/Database/Trait/Attributes.php index 88a53d291..a6334e00c 100644 --- a/src/Database/Trait/Attributes.php +++ b/src/Database/Trait/Attributes.php @@ -915,15 +915,16 @@ private static function withoutIndexedAttribute(array $indexes, string $key): ar continue; } - // Lengths and orders are positional, so drop the entry at the removed attribute's position too. - $position = (int) \array_search($key, $index->attributes, true); + // Lengths and orders are positional, so drop the entry at each removed attribute's position too. $document = $index->toDocument(); $attributes = $index->attributes; $lengths = (array) $document->getAttribute('lengths', []); $orders = (array) $document->getAttribute('orders', []); - \array_splice($attributes, $position, 1); - \array_splice($lengths, $position, 1); - \array_splice($orders, $position, 1); + foreach (\array_reverse(\array_keys($attributes, $key, true)) as $position) { + \array_splice($attributes, $position, 1); + \array_splice($lengths, $position, 1); + \array_splice($orders, $position, 1); + } if ($attributes !== []) { $remaining[] = Index::fromDocument($document