Skip to content
15 changes: 12 additions & 3 deletions src/Database/Adapter/MongoDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,19 @@ public function createCollection(string $name, array $attributes = [], array $in

$collection = $database->$id;

// Mongo creates an index for _id; _uid and _read index by default
// Mongo creates an index for _id; _uid (unique, case insensitive) and _read 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']);


Expand Down
50 changes: 50 additions & 0 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -394,6 +395,14 @@ 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 */
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())
{
Expand Down Expand Up @@ -531,6 +540,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');

foreach ($attributes as $attribute) {
if (\strtolower($attribute->getId()) === \strtolower($id)) {
throw new DuplicateException('Attribute already exists');
}
}

foreach ($attributesInQueue as $attribute) {
if (\strtolower($attribute->getId()) === \strtolower($id)) {
throw new DuplicateException('Attribute already exists in queue');
}
}

if ($format) {
$name = \json_decode($format, true)['name'];
if (!Structure::hasFormat(json_decode($format, true)['name'], $type)) {
Expand Down Expand Up @@ -618,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.');
}
Expand Down Expand Up @@ -710,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,
Expand Down
52 changes: 47 additions & 5 deletions tests/Database/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -190,12 +190,54 @@ 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));
}

/**
* @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 testInvalidDefaultValues
* @depends testIndexQueueCaseInsensitivity
*/
public function testCleanupInvalidDefaultValues()
public function testCleanupAttributeTests()
{
static::getDatabase()->deleteCollection('attributes');
$this->assertEquals(1,1);
Expand Down Expand Up @@ -1466,7 +1508,7 @@ public function testExceptionIndexLimitInQueue()
*/
public function testExceptionDuplicate(Document $document)
{
$this->expectException(Duplicate::class);
$this->expectException(DuplicateException::class);

$document->setAttribute('$id', 'duplicated');

Expand All @@ -1481,7 +1523,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]));

Expand Down