From ac3739dd50918a6827aeade4b543928d3558eeea Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 5 Aug 2021 14:02:57 -0400 Subject: [PATCH 1/8] Change alias to DuplicateException --- tests/Database/Base.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 8a54f5b0d..01cacf32d 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -8,7 +8,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as ExceptionAuthorization; -use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -1418,7 +1418,7 @@ public function testExceptionIndexLimitInQueue() */ public function testExceptionDuplicate(Document $document) { - $this->expectException(Duplicate::class); + $this->expectException(DuplicateException::class); $document->setAttribute('$id', 'duplicated'); @@ -1433,7 +1433,7 @@ public function testExceptionDuplicate(Document $document) */ public function testUniqueIndexDuplicate() { - $this->expectException(Duplicate::class); + $this->expectException(DuplicateException::class); $this->assertEquals(true, static::getDatabase()->createIndex('movies', 'uniqueIndex', Database::INDEX_UNIQUE, ['name'], [128], [Database::ORDER_ASC])); From db1e8722f0e22a20f880dd93cb7e86382f5c1954 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 5 Aug 2021 14:03:54 -0400 Subject: [PATCH 2/8] Check if attribute exists in either document or assoc array --- src/Database/Database.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 10e094a25..9a5a179e1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6,6 +6,7 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Structure; use Utopia\Database\Exception\Authorization as AuthorizationException; +use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Cache\Cache; @@ -382,6 +383,20 @@ public function createAttribute(string $collection, string $id, string $type, in { $collection = $this->getCollection($collection); + // attribute IDs are case insensitive + $attributes = $collection->getAttribute('attributes', []); /** @var Document[] $attributes */ + \array_walk($attributes, function ($attribute) use ($id) { + if ($attribute instanceof Document) { + if (\strtolower($attribute->getId()) === \strtolower($id)) { + throw new Duplicate('Attribute already exists', 400); + } + } else { + if (\strtolower($attribute['$id']) === \strtolower($id)) { + throw new Duplicate('Attribute already exists', 400); + } + } + }); + if ($this->adapter->getAttributeLimit() > 0 && $this->adapter->getAttributeCount($collection, true) >= $this->adapter->getAttributeLimit()) { From d0b9f118a7ff07a50d6754815bfaf7d9e88da167 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 5 Aug 2021 14:04:17 -0400 Subject: [PATCH 3/8] Check for duplicate attribute when adding to queue --- src/Database/Database.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 9a5a179e1..e89587eb7 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -534,6 +534,23 @@ public function addAttributeInQueue(string $collection, string $id, string $type { $collection = $this->getCollection($collection); + /** @var Document[] $attributes */ + /** @var Document[] $attributesInQueue */ + $attributes = $collection->getAttribute('attributes'); + $attributesInQueue = $collection->getAttribute('attributesInQueue'); + + \array_walk($attributes, function ($attribute) use ($id) { + if (\strtolower($attribute->getId()) === \strtolower($id)) { + throw new Duplicate('Attribute already exists', 400); + } + }); + + \array_walk($attributesInQueue, function ($attribute) use ($id) { + if (\strtolower($attribute->getId()) === \strtolower($id)) { + throw new Duplicate('Attribute already exists in queue', 400); + } + }); + if ($format) { $name = \json_decode($format, true)['name']; if (!Structure::hasFormat(json_decode($format, true)['name'], $type)) { From 682151c1c38e4d13779975ee32abb374df734c2f Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 5 Aug 2021 14:04:48 -0400 Subject: [PATCH 4/8] Test for case insensitivity --- tests/Database/Base.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 01cacf32d..76e69b02c 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -178,12 +178,32 @@ public function testInvalidDefaultValues($type, $default) $this->assertEquals(false, static::getDatabase()->createAttribute('attributes', 'bad_default', $type, 256, true, $default)); } + /** + * @depends testInvalidDefaultValues + */ + public function testAttributeCaseInsensitivity() + { + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'caseSensitive', Database::VAR_STRING, 128, true)); + $this->expectException(DuplicateException::class); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'CaseSensitive', Database::VAR_STRING, 128, true)); + } + + /** + * @depends testAttributeCaseInsensitivity + */ + public function testAttributeQueueCaseInsensitivity() + { + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'caseSensitiveInQueue', Database::VAR_STRING, 128, true)); + $this->expectException(DuplicateException::class); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'CaseSensitiveInQueue', Database::VAR_STRING, 128, true)); + } + /** * Ensure the collection is removed after use * - * @depends testInvalidDefaultValues + * @depends testAttributeQueueCaseInsensitivity */ - public function testCleanupInvalidDefaultValues() + public function testCleanupAttributeTests() { static::getDatabase()->deleteCollection('attributes'); $this->assertEquals(1,1); From 724af14915bac17ab909b97ebe4ea459e0468277 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 5 Aug 2021 14:17:18 -0400 Subject: [PATCH 5/8] Use correct alias for duplicate exception --- src/Database/Database.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index e89587eb7..593aaff9b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -388,11 +388,11 @@ public function createAttribute(string $collection, string $id, string $type, in \array_walk($attributes, function ($attribute) use ($id) { if ($attribute instanceof Document) { if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new Duplicate('Attribute already exists', 400); + throw new DuplicateException('Attribute already exists', 400); } } else { if (\strtolower($attribute['$id']) === \strtolower($id)) { - throw new Duplicate('Attribute already exists', 400); + throw new DuplicateException('Attribute already exists', 400); } } }); @@ -541,13 +541,13 @@ public function addAttributeInQueue(string $collection, string $id, string $type \array_walk($attributes, function ($attribute) use ($id) { if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new Duplicate('Attribute already exists', 400); + throw new DuplicateException('Attribute already exists', 400); } }); \array_walk($attributesInQueue, function ($attribute) use ($id) { if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new Duplicate('Attribute already exists in queue', 400); + throw new DuplicateException('Attribute already exists in queue', 400); } }); From 58f2b335a933adfab7d466df9d6d08163b7fb275 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 10 Aug 2021 10:20:14 -0400 Subject: [PATCH 6/8] Remove array walk and instanceof conditions --- src/Database/Database.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 111c89760..73837a119 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -397,17 +397,11 @@ public function createAttribute(string $collection, string $id, string $type, in // attribute IDs are case insensitive $attributes = $collection->getAttribute('attributes', []); /** @var Document[] $attributes */ - \array_walk($attributes, function ($attribute) use ($id) { - if ($attribute instanceof Document) { - if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new DuplicateException('Attribute already exists', 400); - } - } else { - if (\strtolower($attribute['$id']) === \strtolower($id)) { - throw new DuplicateException('Attribute already exists', 400); - } + foreach ($attributes as $attribute) { + if (\strtolower($attribute->getId()) === \strtolower($id)) { + throw new DuplicateException('Attribute already exists'); } - }); + } if ($this->adapter->getAttributeLimit() > 0 && $this->adapter->getAttributeCount($collection, true) >= $this->adapter->getAttributeLimit()) @@ -551,17 +545,17 @@ public function addAttributeInQueue(string $collection, string $id, string $type $attributes = $collection->getAttribute('attributes'); $attributesInQueue = $collection->getAttribute('attributesInQueue'); - \array_walk($attributes, function ($attribute) use ($id) { + foreach ($attributes as $attribute) { if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new DuplicateException('Attribute already exists', 400); + throw new DuplicateException('Attribute already exists'); } - }); + } - \array_walk($attributesInQueue, function ($attribute) use ($id) { + foreach ($attributesInQueue as $attribute) { if (\strtolower($attribute->getId()) === \strtolower($id)) { - throw new DuplicateException('Attribute already exists in queue', 400); + throw new DuplicateException('Attribute already exists in queue'); } - }); + } if ($format) { $name = \json_decode($format, true)['name']; From ea2bf442aeb798a11330afe489a6015384710ff4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 10 Aug 2021 10:35:10 -0400 Subject: [PATCH 7/8] Mongo uid index now case insensitive to match other adapters --- src/Database/Adapter/MongoDB.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 8fb6393d3..0c5146fbd 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -112,10 +112,19 @@ public function createCollection(string $name, array $attributes = [], array $in $collection = $database->$id; - // Mongo creates an index for _id; _uid, _read and _write index by default + // Mongo creates an index for _id; _uid (unique, case insensitive), _read and _write index by default // Returns the name of the created index as a string. - // Update $this->getIndexCount when adding another default index - $uid = $collection->createIndex(['_uid' => $this->getOrder(Database::ORDER_DESC)], ['name' => '_uid', 'unique' => true]); + $uid = $collection->createIndex([ + '_uid' => $this->getOrder(Database::ORDER_DESC)], + [ + 'name' => '_uid', + 'unique' => true, + 'collation' => [ // https://docs.mongodb.com/manual/core/index-case-insensitive/#create-a-case-insensitive-index + 'locale' => 'en', + 'strength' => 1 + ], + ] + ); $read = $collection->createIndex(['_read' => $this->getOrder(Database::ORDER_DESC)], ['name' => '_read_permissions']); $write = $collection->createIndex(['_write' => $this->getOrder(Database::ORDER_DESC)], ['name' => '_write_permissions']); From 07c16ad5b87e417d1fb6d6e7d7f1d1229c00f937 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 10 Aug 2021 10:57:34 -0400 Subject: [PATCH 8/8] IndexIDs should be case insensitive --- src/Database/Database.php | 24 ++++++++++++++++++++++++ tests/Database/Base.php | 24 +++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 73837a119..02cf67564 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -644,6 +644,14 @@ public function createIndex(string $collection, string $id, string $type, array $collection = $this->getCollection($collection); + // index IDs are case insensitive + $indexes = $collection->getAttribute('indexes', []); /** @var Document[] $indexes */ + foreach ($indexes as $index) { + if (\strtolower($index->getId()) === \strtolower($id)) { + throw new DuplicateException('Index already exists'); + } + } + if ($this->adapter->getIndexCount($collection, true) >= $this->adapter->getIndexLimit()) { throw new LimitException('Index limit reached. Cannot create new index.'); } @@ -736,6 +744,22 @@ public function addIndexInQueue(string $collection, string $id, string $type, ar $collection = $this->getCollection($collection); + // index IDs are case insensitive + $indexes = $collection->getAttribute('indexes', []); /** @var Document[] $indexes */ + $indexesInQueue = $collection->getAttribute('indexesInQueue', []); /** @var Document[] $indexesInQueue */ + + foreach ($indexes as $index) { + if (\strtolower($index->getId()) === \strtolower($id)) { + throw new DuplicateException('Index already exists'); + } + } + + foreach ($indexesInQueue as $index) { + if (\strtolower($index->getId()) === \strtolower($id)) { + throw new DuplicateException('Index already exists in queue'); + } + } + $collection->setAttribute('indexesInQueue', new Document([ '$id' => $id, 'type' => $type, diff --git a/tests/Database/Base.php b/tests/Database/Base.php index d7bdcc2d9..7966f191f 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -210,10 +210,32 @@ public function testAttributeQueueCaseInsensitivity() $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'CaseSensitiveInQueue', Database::VAR_STRING, 128, true)); } + /** + * @depends testAttributeQueueCaseInsensitivity + */ + + public function testIndexCaseInsensitivity() + { + $this->assertEquals(true, static::getDatabase()->createIndex('attributes', 'key_caseSensitive', Database::INDEX_KEY, ['caseSensitive'], [128])); + $this->expectException(DuplicateException::class); + $this->assertEquals(true, static::getDatabase()->createIndex('attributes', 'key_CaseSensitive', Database::INDEX_KEY, ['caseSensitive'], [128])); + } + + /** + * @depends testIndexCaseInsensitivity + */ + + public function testIndexQueueCaseInsensitivity() + { + $this->assertEquals(true, static::getDatabase()->createIndex('attributes', 'key_caseSensitiveInQueue', Database::INDEX_KEY, ['caseSensitive'], [128])); + $this->expectException(DuplicateException::class); + $this->assertEquals(true, static::getDatabase()->createIndex('attributes', 'key_CaseSensitiveInQueue', Database::INDEX_KEY, ['caseSensitive'], [128])); + } + /** * Ensure the collection is removed after use * - * @depends testAttributeQueueCaseInsensitivity + * @depends testIndexQueueCaseInsensitivity */ public function testCleanupAttributeTests() {