Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Jan 6, 2026

New attribute support added

Type object

New queries

elemMatch -> for sub element of type array (returns value if atleast one condition matches)
contains/notContains , equals/notEquals -> similar to the behaviour of postgres we already

New index

No new index cause mongodb supports all kind of index on any kind of field.
So added an explicit guard method getSupportForObjectIndex and set to true on postgres as it supports
GIN index on objects

Summary by CodeRabbit

Release Notes

  • New Features

    • Added object attribute support to MongoDB adapter for storing and querying complex object-type data
    • Introduced elemMatch query operator for advanced filtering of nested array elements within objects
    • Enhanced query capabilities with improved nested object attribute filtering
  • Tests

    • Added comprehensive end-to-end tests for object attribute queries and elemMatch functionality

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • 0.69.x

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This PR adds support for object attributes and elemMatch queries to the database library, enabling MongoDB adapter to handle nested object filters through stdClass-to-array conversions, new query operators, and enhanced casting and validation flows.

Changes

Cohort / File(s) Summary
Query Type Additions
src/Database/Query.php
Introduces TYPE_ELEM_MATCH constant and elemMatch() factory method to support array-element matching queries; adds isObjectAttribute field and updates type/logical type registries and isMethod() recognition.
Object Value Detection
src/Database/Database.php
Adds private isCompatibleObjectValue() helper to validate whether value arrays qualify as objects (hashmaps); enhances convertQuery() to route object-compatible values to VAR_OBJECT type when attribute support is unavailable but object support exists.
MongoDB Adapter Object Support
src/Database/Adapter/Mongo.php
Enables object attribute handling via VAR_OBJECT recognition in casting flows (castingBefore, castingAfter); introduces convertStdClassToArray() helper for recursive stdClass-to-array conversion; extends query operators to include $elemMatch and $exists; adds handleObjectFilters() pathway with dot-notation flattening; returns true in getSupportForObject(); converts MongoDB results from stdClass before Document construction.
Query Validation
src/Database/Validator/Queries.php, src/Database/Validator/Query/Filter.php
Registers TYPE_ELEM_MATCH in validator type set; implements specialized Filter validation for elemMatch requiring schema-mode unsupported attributes, attribute existence checks, nested filter queries, and minimum query count validation.
Test Infrastructure & E2E Coverage
tests/e2e/Adapter/Base.php
Disables most test trait mixins (CollectionTests, DocumentTests, etc.), leaving only ObjectAttributeTests active.
Object Attribute Test Helpers
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
Adds private createAttribute() helper to conditionally create attributes only if adapter supports them; gates attribute creation in test flows behind adapter capability checks.
elemMatch Functional Tests
tests/e2e/Adapter/Scopes/SchemalessTests.php
Adds two new e2e tests—testElemMatch() and testElemMatchComplex()—validating elemMatch queries on nested array objects with various filter conditions (equality, comparison, logical operators); includes import for ID helper.

Sequence Diagram

sequenceDiagram
    actor User
    participant QueryBuilder as Query Builder
    participant Database as Database
    participant Validator as Validator
    participant MongoAdapter as MongoDB Adapter
    participant MongoDB as MongoDB

    User->>QueryBuilder: elemMatch('items', [queries])
    QueryBuilder->>QueryBuilder: Create Query(TYPE_ELEM_MATCH)
    QueryBuilder-->>User: Query object

    User->>Database: find(Query)
    Database->>Database: convertQuery() - detect object-compatible values
    Database->>Validator: validate(Query)
    Validator->>Validator: Check elemMatch constraints
    alt Validation Passes
        Validator-->>Database: ✓ Valid
    else Validation Fails
        Validator-->>Database: ✗ Error
    end

    Database->>MongoAdapter: execute query
    MongoAdapter->>MongoAdapter: handleObjectFilters()
    MongoAdapter->>MongoAdapter: flattenWithDotNotation()
    MongoAdapter->>MongoAdapter: Map to MongoDB operators<br/>(TYPE_ELEM_MATCH → $elemMatch)
    MongoAdapter->>MongoDB: Query with $elemMatch
    MongoDB-->>MongoAdapter: stdClass results
    
    rect rgb(240, 248, 255)
        Note over MongoAdapter: Result Conversion Phase
        MongoAdapter->>MongoAdapter: convertStdClassToArray()
        MongoAdapter->>MongoAdapter: Recursively convert stdClass
    end
    
    MongoAdapter-->>Database: Array results
    Database-->>User: Document objects
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Object(json) attribute support for postgres #741 — Implements object (JSON) attribute support with adapter capability signaling and query handling patterns; directly overlaps with this PR's core feature.
  • Feat mongodb #721 — Introduces MongoDB adapter query/casting work and elemMatch operator translation logic that underpins this PR's implementation.
  • Get support for casting  #764 — Modifies MongoDB adapter casting behavior; related through shared casting flow changes for object-type attributes.

Suggested reviewers

  • abnegate
  • fogelito

Poem

🐰 Hops with glee through nested arrays so deep,
elemMatch queries now make MongoDB weep—
Objects converted from classes to arrays in flight,
stdClass to clarity, oh what a sight!
Filtering elements, validating with care,
Rabbit's delight: schema-aware, attribute-aware!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Mongo object support and relevant queries' clearly and directly summarizes the main changes in the PR, which add object attribute support and new query operators (elemMatch and others) to the Mongo adapter.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom Pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ArnabChatterjee20k
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Database/Adapter/Mongo.php (2)

1196-1205: Fix VAR_OBJECT casting to avoid json_decode TypeErrors and satisfy PHPStan

In castingBefore()’s Database::VAR_OBJECT branch, $node can legitimately be an array (or stdClass) coming from user documents, but json_decode($node) unconditionally assumes a string. In PHP 8+ this will trigger a TypeError: json_decode(): Argument #1 ($json) must be of type string, array given. At the same time, convertStdClassToArray() and the docblock just above the schemaless stdClass-normalization loop are triggering static-analysis errors.

You can make object casting robust and clear the PHPStan issues with a small refactor:

Proposed fix for object casting and stdClass conversion
@@ public function castingAfter(Document $collection, Document $document): Document
-        if (!$this->getSupportForAttributes()) {
-            /** @var Document $doc */
-            foreach ($document->getArrayCopy() as $key => $value) {
-                // mongodb results out a stdclass for objects
-                if (is_object($value) && get_class($value) === stdClass::class) {
-                    $document->setAttribute($key, $this->convertStdClassToArray($value));
-                }
-            }
-        }
-        return $document;
-    }
-
-    private function convertStdClassToArray(mixed $value)
-    {
-        if (is_object($value) && get_class($value) === stdClass::class) {
-            return array_map(fn ($v) => $this->convertStdClassToArray($v), get_object_vars($value));
-        }
-
-        if (is_array($value)) {
-            return array_map(
-                fn ($v) => $this->convertStdClassToArray($v),
-                $value
-            );
-        }
-
-        return $value;
-    }
+        if (!$this->getSupportForAttributes()) {
+            foreach ($document->getArrayCopy() as $key => $value) {
+                // MongoDB returns stdClass instances for object fields in schemaless mode
+                if ($value instanceof stdClass) {
+                    $document->setAttribute($key, $this->convertStdClassToArray($value));
+                }
+            }
+        }
+
+        return $document;
+    }
+
+    /**
+     * Recursively convert stdClass instances returned by the Mongo driver into arrays.
+     *
+     * @return mixed
+     */
+    private function convertStdClassToArray(mixed $value): mixed
+    {
+        if ($value instanceof stdClass) {
+            return array_map(fn ($v) => $this->convertStdClassToArray($v), get_object_vars($value));
+        }
+
+        if (is_array($value)) {
+            return array_map(
+                fn ($v) => $this->convertStdClassToArray($v),
+                $value
+            );
+        }
+
+        return $value;
+    }
@@ public function castingBefore(Document $collection, Document $document): Document
-                switch ($type) {
+                switch ($type) {
                     case Database::VAR_DATETIME:
                         if (!($node instanceof UTCDateTime)) {
                             $node = new UTCDateTime(new \DateTime($node));
                         }
                         break;
                     case Database::VAR_OBJECT:
-                        $node = json_decode($node);
-                        $node = $this->convertStdClassToArray($node);
+                        // Accept JSON-encoded strings, stdClass instances, or plain arrays
+                        if (is_string($node)) {
+                            $decoded = \json_decode($node);
+                            if (\json_last_error() === JSON_ERROR_NONE) {
+                                $node = $this->convertStdClassToArray($decoded);
+                            }
+                        } elseif ($node instanceof stdClass || is_array($node)) {
+                            $node = $this->convertStdClassToArray($node);
+                        }
                         break;
                     default:
                         break;
                 }
             }

This prevents runtime TypeErrors for array/object values, ensures Mongo always stores a driver-friendly structure, and resolves the missing return-type and stray @var issues flagged in the pipeline.

Also applies to: 1236-1263, 1276-1303, 1354-1357


2428-2552: Type and recursion fixes for object-filter helpers to satisfy PHPStan

The new object-query path (handleObjectFilters and flattenWithDotNotation) is logically sound, but PHPStan is flagging it for missing return types, missing value types on array parameters/returns, and for passing array_key_first() (which can be int|string|null) into a string parameter.

These issues can be addressed without changing behavior:

Proposed typing and recursion adjustments
@@ protected function buildFilter(Query $query): array
-        $filter = [];
-        if ($query->isObjectAttribute() && in_array($query->getMethod(), [Query::TYPE_EQUAL, Query::TYPE_CONTAINS, Query::TYPE_NOT_CONTAINS, Query::TYPE_NOT_EQUAL])) {
-            $this->handleObjectFilters($query, $filter);
-            return $filter;
-        }
+        $filter = [];
+        if ($query->isObjectAttribute() && in_array($query->getMethod(), [Query::TYPE_EQUAL, Query::TYPE_CONTAINS, Query::TYPE_NOT_CONTAINS, Query::TYPE_NOT_EQUAL], true)) {
+            $this->handleObjectFilters($query, $filter);
+            return $filter;
+        }
@@
-    private function handleObjectFilters(Query $query, array &$filter)
+    /**
+     * @param array<string, mixed> $filter
+     */
+    private function handleObjectFilters(Query $query, array &$filter): void
@@
-        $conditions = [];
-        $isNot = in_array($query->getMethod(), [Query::TYPE_NOT_CONTAINS,Query::TYPE_NOT_EQUAL]);
+        $conditions = [];
+        $isNot = in_array($query->getMethod(), [Query::TYPE_NOT_CONTAINS, Query::TYPE_NOT_EQUAL], true);
@@
-        $values = $query->getValues();
-        foreach ($values as $attribute => $value) {
-            $flattendQuery = $this->flattenWithDotNotation(is_string($attribute) ? $attribute : '', $value);
+        $values = $query->getValues();
+        foreach ($values as $attribute => $value) {
+            $flattendQuery = $this->flattenWithDotNotation(is_string($attribute) ? $attribute : '', $value);
@@
-        $logicalOperator = $isNot ? '$and' : '$or';
+        $logicalOperator = $isNot ? '$and' : '$or';
@@
-    // TODO: check the condition for the multiple keys inside a query validator
-    // example -> [a=>[1,b=>[212]]] shouldn't be allowed
-    // allowed -> [a=>[1,2],b=>[212]]
-    // should be disallowed ->     $data = ['name' => 'doc','role' => ['name'=>['test1','test2'],'ex'=>['new'=>'test1']]];
-    private function flattenWithDotNotation(string $key, mixed $value, string $prefix = ''): array
-    {
-        $result = [];
-        $currentPref = $prefix === '' ? $key : $prefix.'.'.$key;
-        if (is_array($value) && !array_is_list($value)) {
-            $nextKey = array_key_first($value);
-            $result += $this->flattenWithDotNotation($nextKey, $value[$nextKey], $currentPref);
-        }
-        // at the leaf node
-        else {
-            $result[$currentPref] = $value;
-        }
-        return $result;
-    }
+    // TODO: check the condition for the multiple keys inside a query validator
+    // example -> [a=>[1,b=>[212]]] shouldn't be allowed
+    // allowed -> [a=>[1,2],b=>[212]]
+    // should be disallowed -> $data = ['name' => 'doc','role' => ['name'=>['test1','test2'],'ex'=>['new'=>'test1']]];
+    /**
+     * Flatten a nested associative array into a single "dot notation" key.
+     *
+     * @return array<string, mixed>
+     */
+    private function flattenWithDotNotation(string $key, mixed $value, string $prefix = ''): array
+    {
+        $result = [];
+        $currentPref = $prefix === '' ? $key : $prefix . '.' . $key;
+
+        if (is_array($value) && !array_is_list($value)) {
+            $nextKey = array_key_first($value);
+            if (\is_string($nextKey)) {
+                $result += $this->flattenWithDotNotation($nextKey, $value[$nextKey], $currentPref);
+            }
+        } else {
+            // Leaf node
+            $result[$currentPref] = $value;
+        }
+
+        return $result;
+    }

This should clear all the reported PHPStan complaints around these methods while preserving the current object-query behavior.

🤖 Fix all issues with AI Agents
In @tests/e2e/Adapter/Base.php:
- Around line 26-38: Uncomment all of the test trait uses in the Base.php class
so every test suite is enabled (currently only ObjectAttributeTests is active);
specifically restore the use statements for CollectionTests,
CustomDocumentTypeTests, DocumentTests, AttributeTests, IndexTests,
OperatorTests, PermissionTests, RelationshipTests, SpatialTests,
SchemalessTests, VectorTests, and GeneralTests alongside ObjectAttributeTests so
the full test coverage runs as intended.
🧹 Nitpick comments (5)
src/Database/Query.php (1)

131-131: Unused property $isObjectAttribute.

The property $isObjectAttribute is declared but never used in this file. The getter method isObjectAttribute() at lines 991-994 returns $this->attributeType === Database::VAR_OBJECT instead of using this property. Consider removing this unused property or clarifying its intended purpose.

🔎 Proposed fix to remove unused property
-    protected bool $isObjectAttribute = false;
src/Database/Database.php (2)

653-666: Mongo VAR_OBJECT decode behaviour looks correct; minor doc / clarity nits

Handling non‑string values by short‑circuiting (for Mongo storing native objects/arrays) is sensible and preserves existing JSON‑string behaviour for other adapters. Two small polish points:

  • The docblock says @return array|null but the function can return non‑arrays (e.g. a Mongo driver object or scalar when json_decode fails). Consider widening the documented return type to mixed (or array|scalar|null) to avoid misleading static analysis.
  • For readability you may want to explicitly return null; in the is_null($value) branch instead of bare return;.

These don’t affect runtime behaviour but would tighten the contract.


8120-8155: Tidy up docblock attachment and no‑op loop in isCompatibleObjectValue / convertQuery

The new schemaless object‑type detection is a nice addition and the heuristic itself is reasonable, but there are a couple of small cleanups worth doing:

  1. Docblock now detached from convertQuery

    The original convertQuery docblock (lines 8113‑8119) is now separated from the convertQuery method by the new isCompatibleObjectValue docblock and method. In PHP, only the last docblock immediately preceding a symbol is associated with it, so convertQuery effectively lost its docblock and the earlier one is “orphaned”.

    Suggestion: move isCompatibleObjectValue either above the convertQueries method or below convertQuery, and keep a single docblock immediately above each method.

  2. Inner loop in isCompatibleObjectValue is currently dead code

    foreach ($value as $nestedValue) {
        if (\is_array($nestedValue)) {
            continue;
        }
    }

    This loop doesn’t alter any state or return early, so it has no effect. Either remove it, or, if you intended to enforce a stricter rule (e.g. “all nested values must also be arrays”), add the corresponding return false / condition.

A possible refactor sketch:

Suggested cleanup
-    /**
-     * @param Document $collection
-     * @param Query $query
-     * @return Query
-     * @throws QueryException
-     * @throws \Utopia\Database\Exception
-     */
-    /**
-     * Check if values are compatible with object attribute type (hashmap/multi-dimensional array)
-     *
-     * @param array<mixed> $values
-     * @return bool
-     */
-    private function isCompatibleObjectValue(array $values): bool
-    {
+    /**
+     * Check if values are compatible with object attribute type (hashmap/multi-dimensional array)
+     *
+     * @param array<mixed> $values
+     * @return bool
+     */
+    private function isCompatibleObjectValue(array $values): bool
+    {
         if (empty($values)) {
             return false;
         }
 
         foreach ($values as $value) {
             if (!\is_array($value)) {
                 return false;
             }
 
             // Check associative array (hashmap) or nested structure
             if (empty($value)) {
                 continue;
             }
 
             // simple indexed array => not an object
             if (\array_keys($value) === \range(0, \count($value) - 1)) {
                 return false;
             }
-
-            foreach ($value as $nestedValue) {
-                if (\is_array($nestedValue)) {
-                    continue;
-                }
-            }
         }
 
         return true;
     }
+
+    /**
+     * @param Document $collection
+     * @param Query $query
+     * @return Query
+     * @throws QueryException
+     * @throws \Utopia\Database\Exception
+     */
+    public function convertQuery(Document $collection, Query $query): Query

The new elseif (!$this->adapter->getSupportForAttributes()) { … } branch itself looks good: it only runs in schemaless mode, is guarded by getSupportForObject(), and won’t mis‑classify nested or non‑array queries since isCompatibleObjectValue fails fast on non‑arrays. Based on learnings, this matches the Mongo schemaless behaviour.

Also applies to: 8193-8198

tests/e2e/Adapter/Scopes/SchemalessTests.php (1)

13-1371: ElemMatch tests are thorough; remove unused local variables

The new schemaless elemMatch tests cover the intended semantics well (AND-composed inner conditions, negative cases, mixed scalar/boolean queries). The only issue is the unused $doc1, $doc2, and $doc3 locals, which static analysis correctly flags and which can be removed without changing behavior.

Proposed diff to drop unused locals
@@
-        // Create documents with array of objects
-        $doc1 = $database->createDocument($collectionId, new Document([
+        // Create documents with array of objects
+        $database->createDocument($collectionId, new Document([
@@
-        $doc2 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
@@
-        $doc3 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
@@
-        // Create documents with complex nested structures
-        $doc1 = $database->createDocument($collectionId, new Document([
+        // Create documents with complex nested structures
+        $database->createDocument($collectionId, new Document([
@@
-        $doc2 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
src/Database/Adapter/Mongo.php (1)

2010-2033: Normalize stdClass across all cursor batches in find()

find() now wraps the first batch in convertStdClassToArray(), but subsequent getMore batches still construct Document from the raw $record array. That can leave nested stdClass instances in some results but not others, depending on pagination, which is subtle and hard to debug for consumers.

You can make behavior consistent by applying the same conversion in the getMore loop:

Proposed diff for subsequent batches
-                foreach ($moreResults as $result) {
-                    $record = $this->replaceChars('_', '$', (array)$result);
-                    $found[] = new Document($record);
-                }
+                foreach ($moreResults as $result) {
+                    $record = $this->replaceChars('_', '$', (array)$result);
+                    $found[] = new Document($this->convertStdClassToArray($record));
+                }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8c1b2f and e4f00f3.

📒 Files selected for processing (8)
  • src/Database/Adapter/Mongo.php
  • src/Database/Database.php
  • src/Database/Query.php
  • src/Database/Validator/Queries.php
  • src/Database/Validator/Query/Filter.php
  • tests/e2e/Adapter/Base.php
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • tests/e2e/Adapter/Scopes/SchemalessTests.php
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 747
File: src/Database/Adapter/Mongo.php:1449-1453
Timestamp: 2025-10-29T12:27:57.071Z
Learning: In src/Database/Adapter/Mongo.php, when getSupportForAttributes() returns false (schemaless mode), the updateDocument method intentionally uses a raw document without $set operator for replacement-style updates, as confirmed by the repository maintainer ArnabChatterjee20k.
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6418-6439
Timestamp: 2025-10-03T02:04:17.803Z
Learning: In tests/e2e/Adapter/Scopes/DocumentTests::testSchemalessDocumentInvalidInteralAttributeValidation (PHP), when the adapter reports getSupportForAttributes() === false (schemaless), the test should not expect exceptions from createDocuments for “invalid” internal attributes; remove try/catch and ensure the test passes without exceptions, keeping at least one assertion.
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6418-6439
Timestamp: 2025-10-03T02:04:17.803Z
Learning: In tests/e2e/Adapter/Scopes/DocumentTests::testSchemalessDocumentInvalidInteralAttributeValidation (PHP), when the adapter reports getSupportForAttributes() === false (schemaless), the test should not expect exceptions from createDocuments for “invalid” internal attributes; remove try/catch and ensure the test passes without exceptions, keeping at least one assertion.

Applied to files:

  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • src/Database/Validator/Query/Filter.php
  • tests/e2e/Adapter/Scopes/SchemalessTests.php
  • tests/e2e/Adapter/Base.php
  • src/Database/Adapter/Mongo.php
📚 Learning: 2025-10-29T12:27:57.071Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 747
File: src/Database/Adapter/Mongo.php:1449-1453
Timestamp: 2025-10-29T12:27:57.071Z
Learning: In src/Database/Adapter/Mongo.php, when getSupportForAttributes() returns false (schemaless mode), the updateDocument method intentionally uses a raw document without $set operator for replacement-style updates, as confirmed by the repository maintainer ArnabChatterjee20k.

Applied to files:

  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • tests/e2e/Adapter/Scopes/SchemalessTests.php
  • src/Database/Adapter/Mongo.php
📚 Learning: 2025-10-16T09:37:33.531Z
Learnt from: fogelito
Repo: utopia-php/database PR: 733
File: src/Database/Adapter/MariaDB.php:1801-1806
Timestamp: 2025-10-16T09:37:33.531Z
Learning: In the MariaDB adapter (src/Database/Adapter/MariaDB.php), only duplicate `_uid` violations should throw `DuplicateException`. All other unique constraint violations, including `PRIMARY` key collisions on the internal `_id` field, should throw `UniqueException`. This is the intended design to distinguish between user-facing document duplicates and internal/user-defined unique constraint violations.

Applied to files:

  • tests/e2e/Adapter/Scopes/SchemalessTests.php
🧬 Code graph analysis (5)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (4)
src/Database/Adapter/Mongo.php (2)
  • createAttribute (667-670)
  • getSupportForAttributes (2758-2761)
src/Database/Adapter/Postgres.php (1)
  • createAttribute (457-486)
src/Database/Adapter/SQL.php (2)
  • createAttribute (247-261)
  • getSupportForAttributes (949-952)
src/Database/Adapter/Pool.php (2)
  • createAttribute (168-171)
  • getSupportForAttributes (328-331)
src/Database/Validator/Query/Filter.php (2)
src/Database/Query.php (3)
  • Query (8-1195)
  • groupByType (849-931)
  • getValues (184-187)
src/Database/Validator/Query/Order.php (1)
  • isValidAttribute (30-55)
src/Database/Validator/Queries.php (1)
src/Database/Query.php (1)
  • Query (8-1195)
src/Database/Adapter/Mongo.php (2)
src/Database/Database.php (1)
  • Database (37-8824)
src/Database/Query.php (1)
  • Query (8-1195)
src/Database/Database.php (3)
src/Database/Adapter.php (2)
  • getSupportForAttributes (923-923)
  • getSupportForObject (1080-1080)
src/Database/Query.php (2)
  • getValues (184-187)
  • setAttributeType (968-971)
src/Database/Operator.php (1)
  • getValues (155-158)
🪛 GitHub Actions: CodeQL
src/Database/Adapter/Mongo.php

[error] 1278-1278: PHPStan: Variable $doc in PHPDoc tag @var does not match any variable in the foreach loop: $key, $value


[error] 1288-1288: PHPStan: Method Utopia\Database\Adapter\Mongo::convertStdClassToArray() has no return type specified.


[error] 2492-2492: PHPStan: Method Utopia\Database\Adapter\Mongo::handleObjectFilters() has no return type specified.


[error] 2492-2492: PHPStan: Method Utopia\Database\Adapter\Mongo::handleObjectFilters() has parameter $filter with no value type specified in iterable type array.


[error] 2539-2539: PHPStan: Method Utopia\Database\Adapter\Mongo::flattenWithDotNotation() return type has no value type specified in iterable type array.


[error] 2545-2545: PHPStan: Parameter #1 $key of method Utopia\Database\Adapter\Mongo::flattenWithDotNotation() expects string, int|string|null given.

🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/SchemalessTests.php

1177-1177: Avoid unused local variables such as '$doc1'. (undefined)

(UnusedLocalVariable)


1186-1186: Avoid unused local variables such as '$doc2'. (undefined)

(UnusedLocalVariable)


1195-1195: Avoid unused local variables such as '$doc3'. (undefined)

(UnusedLocalVariable)


1330-1330: Avoid unused local variables such as '$doc1'. (undefined)

(UnusedLocalVariable)


1339-1339: Avoid unused local variables such as '$doc2'. (undefined)

(UnusedLocalVariable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Setup & Build Docker Image
🔇 Additional comments (6)
src/Database/Query.php (2)

65-65: LGTM!

The TYPE_ELEM_MATCH constant is properly integrated across all relevant arrays (TYPES, LOGICAL_TYPES) and the isMethod() validation, ensuring consistent recognition as a logical query type.

Also applies to: 112-112, 124-124, 297-297


1186-1194: LGTM!

The elemMatch() factory method follows the same pattern as the existing logical query methods (or() and and()), correctly creating a Query instance with TYPE_ELEM_MATCH and nested queries.

src/Database/Validator/Queries.php (1)

110-110: LGTM!

The TYPE_ELEM_MATCH is correctly mapped to Base::METHOD_TYPE_FILTER, consistent with other filter query types. This allows the validator to properly handle elemMatch queries during validation.

src/Database/Validator/Query/Filter.php (1)

399-423: ElemMatch validation for schemaless mode looks correct

TYPE_ELEM_MATCH is correctly rejected when attributes are supported (schema mode), requires the target attribute to be valid, and enforces that all nested entries are filter queries with at least one query present. This is consistent with existing AND/OR handling and the schemaless-only design for elemMatch; no changes needed.

tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1)

18-40: Object-attribute helper and capability gating are appropriate

Wrapping createAttribute behind getSupportForAttributes() and reusing it across tests keeps the scenarios working for adapters with and without schema support, while the updated skips (requiring both getSupportForObject() and getSupportForAttributes() for index/invalid/defaults tests) prevent running schema-only cases against schemaless adapters. This matches the intended split between schemaless and schema modes and looks good as-is.

Also applies to: 56-57, 583-585, 693-695, 869-914, 980-983

src/Database/Adapter/Mongo.php (1)

2369-2384: ElemMatch translation and object support flag look consistent with the new features

The buildFilters() special-case for TYPE_ELEM_MATCH correctly wraps nested filters under Mongo’s $elemMatch operator, and the new mapping in getQueryOperator() ensures the validator/adapter pipeline recognizes it as a first-class operator. Enabling getSupportForObject() to return true matches the added object-attribute casting and filter handling and aligns the Mongo adapter with the new object-query features.

Also applies to: 2584-2586, 2904-2907

*
* @return bool
*/
abstract public function getSupportForIndexObject(): bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, I think it should be getSupportForObjectIndexes, double check the other methods

break;
case Database::VAR_OBJECT:
$node = json_decode($node);
$node = $this->convertStdClassToArray($node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it redundant? Since we already have an array from json_decode

Comment on lines 2560 to 2573
if (\is_array($value) && !\array_is_list($value)) {
$nextKey = \array_key_first($value);
if ($nextKey === null) {
return $result;
}

$nextKeyString = (string) $nextKey;
$result += $this->flattenWithDotNotation($nextKeyString, $value[$nextKey], $currentPref);
} else {
// at the leaf node
$result[$currentPref] = $value;
}

return $result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a loop instead of recursion here

@abnegate abnegate merged commit 43505a1 into 3.x Jan 9, 2026
18 checks passed
@abnegate abnegate deleted the mongo-object branch January 9, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants