Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
78e99a3
Merge remote-tracking branch 'upstream/main' into attributes-fix-sche…
ArnabChatterjee20k Sep 25, 2025
42eca73
updated index validator
ArnabChatterjee20k Sep 25, 2025
8d2583e
updated selection attributes for schemales
ArnabChatterjee20k Sep 25, 2025
ef7363a
linting
ArnabChatterjee20k Sep 25, 2025
6a36761
* updated increment decrement db operation
ArnabChatterjee20k Sep 26, 2025
da2a82b
Merge remote-tracking branch 'upstream/feat-mongo-tmp' into attribute…
ArnabChatterjee20k Sep 28, 2025
b0855b6
synced with feat-mongo-tmp branch
ArnabChatterjee20k Sep 28, 2025
c513d9b
Merge remote-tracking branch 'upstream/feat-mongo-tmp' into attribute…
ArnabChatterjee20k Sep 29, 2025
0c12424
updated tests
ArnabChatterjee20k Sep 29, 2025
84cbadf
added document mutation fix for the creaetDocuments and tests for sch…
ArnabChatterjee20k Sep 29, 2025
f52c1e9
Merge remote-tracking branch 'upstream/feat-mongo-tmp' into attribute…
ArnabChatterjee20k Sep 29, 2025
0dceeee
reverted changes
ArnabChatterjee20k Sep 30, 2025
f0729d4
* updated filters to prioritize the max query values validation first
ArnabChatterjee20k Sep 30, 2025
2f6ea98
Merge remote-tracking branch 'upstream/feat-mongo-tmp' into attribute…
ArnabChatterjee20k Sep 30, 2025
4103174
linting
ArnabChatterjee20k Sep 30, 2025
d700b45
Merge remote-tracking branch 'upstream/feat-mongodb' into attributes-…
ArnabChatterjee20k Oct 6, 2025
b9e408a
updated lock
ArnabChatterjee20k Oct 6, 2025
64135d9
pr followups
ArnabChatterjee20k Oct 6, 2025
8dbfb0d
* updated regex in contains if array to createSafeRegex
ArnabChatterjee20k Oct 6, 2025
e04a6ff
linting
ArnabChatterjee20k Oct 6, 2025
1ce871b
linting
ArnabChatterjee20k Oct 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 31 additions & 10 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Timeout as TimeoutException;
use Utopia\Database\Exception\Transaction as TransactionException;
use Utopia\Database\Exception\Type as TypeException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Mongo\Client;
Expand Down Expand Up @@ -1655,15 +1656,19 @@ public function increaseDocumentAttribute(string $collection, string $id, string
}

$options = $this->getTransactionOptions();
$this->client->update(
$this->getNamespace() . '_' . $this->filter($collection),
$filters,
[
'$inc' => [$attribute => $value],
'$set' => ['_updatedAt' => $this->toMongoDatetime($updatedAt)],
],
options: $options
);
try {
$this->client->update(
$this->getNamespace() . '_' . $this->filter($collection),
$filters,
[
'$inc' => [$attribute => $value],
'$set' => ['_updatedAt' => $this->toMongoDatetime($updatedAt)],
],
options: $options
);
} catch (MongoException $e) {
throw $this->processException($e);
}

return true;
}
Expand Down Expand Up @@ -2323,7 +2328,18 @@ protected function buildFilter(Query $query): array
$filter[$attribute]['$nin'] = $value;
} elseif ($operator == '$in') {
if ($query->getMethod() === Query::TYPE_CONTAINS && !$query->onArray()) {
$filter[$attribute]['$regex'] = $this->createSafeRegex($value, '.*%s.*');
// contains support array values
if (is_array($value)) {
$filter['$or'] = array_map(function ($val) use ($attribute) {
return [
$attribute => [
'$regex' => $this->createSafeRegex($val, '.*%s.*', 'i')
]
];
}, $value);
} else {
$filter[$attribute]['$regex'] = $this->createSafeRegex($value, '.*%s.*');
}
} else {
$filter[$attribute]['$in'] = $query->getValues();
}
Expand Down Expand Up @@ -2965,6 +2981,11 @@ protected function processException(Exception $e): \Exception
return new TransactionException('No active transaction', $e->getCode(), $e);
}

// Invalid operation(MongoDB error code 14)
if ($e->getCode() === 14) {
return new TypeException('Invalid operation', $e->getCode(), $e);
}

return $e;
}

Expand Down
82 changes: 45 additions & 37 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -3487,7 +3487,7 @@ public function getDocument(string $collection, string $id, array $queries = [],
$this->checkQueriesType($queries);

if ($this->validate) {
$validator = new DocumentValidator($attributes);
$validator = new DocumentValidator($attributes, $this->adapter->getSupportForAttributes());
if (!$validator->isValid($queries)) {
throw new QueryException($validator->getDescription());
}
Expand Down Expand Up @@ -5019,6 +5019,7 @@ public function updateDocuments(
$this->maxQueryValues,
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);

if (!$validator->isValid($queries)) {
Expand Down Expand Up @@ -5892,24 +5893,25 @@ public function increaseDocumentAttribute(
}

$collection = $this->silent(fn () => $this->getCollection($collection));
if ($this->adapter->getSupportForAttributes()) {
$attr = \array_filter($collection->getAttribute('attributes', []), function ($a) use ($attribute) {
return $a['$id'] === $attribute;
});

$attr = \array_filter($collection->getAttribute('attributes', []), function ($a) use ($attribute) {
return $a['$id'] === $attribute;
});

if (empty($attr)) {
throw new NotFoundException('Attribute not found');
}
if (empty($attr)) {
throw new NotFoundException('Attribute not found');
}

$whiteList = [
self::VAR_INTEGER,
self::VAR_FLOAT
];
$whiteList = [
self::VAR_INTEGER,
self::VAR_FLOAT
];

/** @var Document $attr */
$attr = \end($attr);
if (!\in_array($attr->getAttribute('type'), $whiteList) || $attr->getAttribute('array')) {
throw new TypeException('Attribute must be an integer or float and can not be an array.');
/** @var Document $attr */
$attr = \end($attr);
if (!\in_array($attr->getAttribute('type'), $whiteList) || $attr->getAttribute('array')) {
throw new TypeException('Attribute must be an integer or float and can not be an array.');
}
}

$document = $this->withTransaction(function () use ($collection, $id, $attribute, $value, $max) {
Expand Down Expand Up @@ -5990,25 +5992,27 @@ public function decreaseDocumentAttribute(

$collection = $this->silent(fn () => $this->getCollection($collection));

$attr = \array_filter($collection->getAttribute('attributes', []), function ($a) use ($attribute) {
return $a['$id'] === $attribute;
});
if ($this->adapter->getSupportForAttributes()) {
$attr = \array_filter($collection->getAttribute('attributes', []), function ($a) use ($attribute) {
return $a['$id'] === $attribute;
});

if (empty($attr)) {
throw new NotFoundException('Attribute not found');
}
if (empty($attr)) {
throw new NotFoundException('Attribute not found');
}

$whiteList = [
self::VAR_INTEGER,
self::VAR_FLOAT
];
$whiteList = [
self::VAR_INTEGER,
self::VAR_FLOAT
];

/**
* @var Document $attr
*/
$attr = \end($attr);
if (!\in_array($attr->getAttribute('type'), $whiteList) || $attr->getAttribute('array')) {
throw new TypeException('Attribute must be an integer or float and can not be an array.');
/**
* @var Document $attr
*/
$attr = \end($attr);
if (!\in_array($attr->getAttribute('type'), $whiteList) || $attr->getAttribute('array')) {
throw new TypeException('Attribute must be an integer or float and can not be an array.');
}
}

$document = $this->withTransaction(function () use ($collection, $id, $attribute, $value, $min) {
Expand Down Expand Up @@ -6563,7 +6567,8 @@ public function deleteDocuments(
$this->adapter->getIdAttributeType(),
$this->maxQueryValues,
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime()
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);

if (!$validator->isValid($queries)) {
Expand Down Expand Up @@ -6992,6 +6997,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$this->maxQueryValues,
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$validator->isValid($queries)) {
throw new QueryException($validator->getDescription());
Expand Down Expand Up @@ -7056,6 +7062,7 @@ public function sum(string $collection, string $attribute, array $queries = [],
$this->maxQueryValues,
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$validator->isValid($queries)) {
throw new QueryException($validator->getDescription());
Expand Down Expand Up @@ -7460,10 +7467,11 @@ private function validateSelections(Document $collection, array $queries): array
$keys[] = $attribute['key'] ?? $attribute['$id'];
}
}

$invalid = \array_diff($selections, $keys);
if (!empty($invalid) && !\in_array('*', $invalid)) {
throw new QueryException('Cannot select attributes: ' . \implode(', ', $invalid));
if ($this->adapter->getSupportForAttributes()) {
$invalid = \array_diff($selections, $keys);
if (!empty($invalid) && !\in_array('*', $invalid)) {
throw new QueryException('Cannot select attributes: ' . \implode(', ', $invalid));
}
}

$selections = \array_merge($selections, $relationshipSelections);
Expand Down
4 changes: 4 additions & 0 deletions src/Database/Validator/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ public function checkIndexLength(Document $index): bool
return true;
}

if (!$this->supportForAttributes) {
return true;
}

$total = 0;
$lengths = $index->getAttribute('lengths', []);
$attributes = $index->getAttribute('attributes', []);
Expand Down
5 changes: 3 additions & 2 deletions src/Database/Validator/Queries/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ class Document extends Queries
{
/**
* @param array<mixed> $attributes
* @param bool $supportForAttributes
* @throws Exception
*/
public function __construct(array $attributes)
public function __construct(array $attributes, $supportForAttributes = true)
{
$attributes[] = new \Utopia\Database\Document([
'$id' => '$id',
Expand All @@ -35,7 +36,7 @@ public function __construct(array $attributes)
]);

$validators = [
new Select($attributes),
new Select($attributes, $supportForAttributes),
];

parent::__construct($validators);
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Validator/Query/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ protected function isValidAttributeAndValues(string $attribute, array $values, s
return false;
}

if (!$this->supportForAttributes && !isset($this->schema[$attribute])) {
return true;
}
$attributeSchema = $this->schema[$attribute];

$attributeType = $attributeSchema['type'];

// If the query method is spatial-only, the attribute must be a spatial type
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/Adapter/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Tests\E2E\Adapter\Scopes\IndexTests;
use Tests\E2E\Adapter\Scopes\PermissionTests;
use Tests\E2E\Adapter\Scopes\RelationshipTests;
use Tests\E2E\Adapter\Scopes\SchemalessTests;
use Tests\E2E\Adapter\Scopes\SpatialTests;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
Expand All @@ -25,6 +26,7 @@ abstract class Base extends TestCase
use PermissionTests;
use RelationshipTests;
use SpatialTests;
use SchemalessTests;
use GeneralTests;

protected static string $namespace;
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/Adapter/Scopes/AttributeTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,7 @@ public function testArrayAttribute(): void
));

if ($database->getAdapter()->getSupportForIndexArray()) {
if ($database->getAdapter()->getMaxIndexLength() > 0) {
if ($database->getAdapter()->getSupportForAttributes() && $database->getAdapter()->getMaxIndexLength() > 0) {
// If getMaxIndexLength() > 0 We clear length for array attributes
$database->createIndex($collection, 'indx1', Database::INDEX_KEY, ['long_size'], [], []);
$database->deleteIndex($collection, 'indx1');
Expand Down
Loading