Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Oct 17, 2025

Summary by CodeRabbit

Release Notes

New Features

  • Added support for object (JSON) attributes in collections
  • Object attributes support equality, inequality, and containment query operations
  • PostgreSQL now enables GIN indexes on object attributes for optimized querying

Tests

  • Added comprehensive end-to-end and unit tests for object attribute functionality across database adapters

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

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

This pull request introduces comprehensive object (JSON) attribute support across the database layer. It adds abstract method declarations in the base Adapter class, implements support indicators across all adapters (with Postgres providing full JSONB query and GIN index capabilities), adds VAR_OBJECT and INDEX_OBJECT constants to the Database class, validates object attributes and indexes, integrates object type handling into query filtering and casting flows, and includes extensive end-to-end and unit tests covering object attribute lifecycle, indexes, defaults, and error cases.

Changes

Cohort / File(s) Summary
Core Adapter Interface & Implementations
src/Database/Adapter.php, src/Database/Adapter/MariaDB.php, src/Database/Adapter/Postgres.php, src/Database/Adapter/SQLite.php, src/Database/Adapter/Mongo.php, src/Database/Adapter/Pool.php
Added abstract method getSupportForObject(): bool in base Adapter class and implemented in all adapters. Postgres supports full object handling; MariaDB, SQLite, and Mongo return false; Pool delegates to underlying adapter.
SQL Width Estimation
src/Database/Adapter/SQL.php
Added case for Database::VAR_OBJECT in width calculation, incrementing by 20 bytes to account for JSON pointer storage.
Database Core
src/Database/Database.php
Added VAR_OBJECT and INDEX_OBJECT constants. Extended validation for attributes and indexes to support object types, including adapter support checks, empty size enforcement, and constraint validation. Integrated object type into casting/decoding flows for JSON conversion.
Query Support
src/Database/Query.php
Added isObjectAttribute(): bool predicate method to detect object-type attributes.
Validators - Query & Structure
src/Database/Validator/Query/Filter.php, src/Database/Validator/Structure.php
Extended attribute-value validation to accept arrays for object types. Added ObjectValidator for object-type validation in Structure validator. Updated contains/notContains to allow object-type attributes.
Index Validator
src/Database/Validator/Index.php
Added constructor parameter $supportForObjectIndexes and new checkObjectIndexes(Document $index): bool method enforcing single object attribute, no explicit orders, and type validation.
Object Validator
src/Database/Validator/ObjectValidator.php
New validator class validating object types as non-list arrays with description "Value must be a valid object".
E2E Tests - Core
tests/e2e/Adapter/Base.php, tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
Added ObjectAttributeTests trait to Base test class. Comprehensive trait covering object attribute creation, querying (equal, notEqual, contains, notContains), updates, deletions, defaults, GIN index creation and validation, and edge cases. Skips tests if adapter lacks object support.
E2E Tests - Updates
tests/e2e/Adapter/Scopes/AttributeTests.php, tests/e2e/Adapter/Scopes/DocumentTests.php
Updated error message expectations to include "object" as valid type in contains/notContains validation for non-array/non-string attributes.
Unit Tests
tests/unit/Validator/IndexTest.php, tests/unit/Validator/ObjectTest.php
Added testGinIndexValidation() for validating object index constraints. Added ObjectTest class with tests for associative arrays, invalid structures, and empty cases.

Sequence Diagram

sequenceDiagram
    actor User
    participant Query as Query Handler
    participant Database
    participant Adapter as Adapter<br/>(Postgres)
    participant SQL as SQL Engine

    User->>Query: Create query with object attribute
    Query->>Database: Validate attribute & check adapter support
    Database->>Adapter: getSupportForObject()
    Adapter-->>Database: true/false
    alt Adapter supports objects
        Database->>Adapter: Handle object query (getSQLCondition)
        Adapter->>Adapter: handleObjectQueries()
        Note over Adapter: Parse TYPE_EQUAL,<br/>TYPE_CONTAINS, etc.
        Adapter-->>Database: JSONB condition + binds
        Database->>SQL: Execute with JSONB operators
        SQL-->>Database: Result rows
    else Adapter does not support objects
        Database-->>User: Error: unsupported
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Postgres adapter (handleObjectQueries method): Requires verification of JSONB query operators (TYPE_EQUAL, TYPE_CONTAINS, etc.) and SQL syntax for nested object queries.
  • Database.php validation logic: Multiple validation paths (attribute creation/update, index creation, casting) need tracing to ensure object type flows correctly and adapter support is consistently checked.
  • Index validator changes (checkObjectIndexes): Verify constraint enforcement (single attribute, no explicit orders) and interaction with existing index validation.
  • Duplicate method in Pool.php: The summary mentions potential duplication of getSupportForObject() method—verify this is not a merge artifact.

Possibly related PRs

Suggested reviewers

  • abnegate
  • fogelito

Poem

🐰 Hop along with objects new,
JSONB fields and GIN indexes too,
Arrays nested in pointers small,
Postgres handles them all!
Test and validate with care,
Object support everywhere! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title "Object(json) attribute support for postgres" is partially accurate but misleading in scope. While the changeset does add comprehensive object/JSON attribute support to the Postgres adapter (including JSONB handling, query operations, and GIN indexing), the changes are significantly broader than the title suggests. The PR introduces system-wide object type support by adding VAR_OBJECT and INDEX_OBJECT constants to the core Database class, creating new ObjectValidator, extending Query class with isObjectAttribute(), updating all database adapters with getSupportForObject() methods (MariaDB, SQLite, Mongo, Pool, and Adapter base), and adding extensive validation and testing infrastructure. A developer reviewing the title alone would likely underestimate the scope and significance of these architectural changes, expecting adapter-specific modifications rather than a feature addition across the entire database abstraction layer. The title should be revised to reflect the system-wide nature of the changes while acknowledging Postgres as the primary implementation. Consider a more accurate title such as "Add object attribute type support with Postgres JSONB implementation" or "Implement object(json) attributes across database system with Postgres support" to clearly convey that this is a foundational feature addition beyond adapter-specific modifications.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

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: 7

Caution

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

⚠️ Outside diff range comments (1)
src/Database/Validator/Query/Filter.php (1)

209-219: notContains allowed here, but Postgres doesn’t implement it for objects

This path allows CONTAINS/NOT_CONTAINS on non-array attributes when type is string/object/spatial. Postgres::handleObjectQueries supports CONTAINS but not NOT_CONTAINS (nor NOT_EQUAL), so NOT_CONTAINS on an object will throw at runtime.

Either: (a) implement NOT_CONTAINS (and NOT_EQUAL) in Postgres, or (b) reject NOT_CONTAINS for objects here.

Here’s option (a) preferred for parity:

  • Postgres: add NOT_CONTAINS using NOT (col @> :val::jsonb); add NOT_EQUAL using NOT (col = :val::jsonb); keep multi‑value separator consistent with NOT queries (AND).

If you prefer option (b), I can provide a compact guard here to block NOT_CONTAINS for object attributes.

🧹 Nitpick comments (2)
src/Database/Adapter/SQLite.php (1)

976-979: Capability flag implemented correctly

SQLite advertises no object/JSON support, consistent with current storage-as-text behavior in create/update. No concerns.

If/when JSON1 usage is introduced, consider feature-detecting it and flipping this to true behind a config/feature flag.

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

2019-2027: Capability flag advertises JSONB support

Looks good. As a follow-up, consider first-class JSONB indexing for performance on @> queries (GIN).

I can draft a proposal to leverage $indexAttributeTypes inside createIndex() to emit USING GIN ("col") for object attributes.

📜 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 141338a and 4483496.

📒 Files selected for processing (11)
  • src/Database/Adapter.php (1 hunks)
  • src/Database/Adapter/MariaDB.php (1 hunks)
  • src/Database/Adapter/Postgres.php (5 hunks)
  • src/Database/Adapter/SQL.php (1 hunks)
  • src/Database/Adapter/SQLite.php (1 hunks)
  • src/Database/Database.php (7 hunks)
  • src/Database/Query.php (1 hunks)
  • src/Database/Validator/Query/Filter.php (2 hunks)
  • src/Database/Validator/Structure.php (1 hunks)
  • tests/e2e/Adapter/Scopes/AttributeTests.php (1 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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/AttributeTests.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
🧬 Code graph analysis (10)
src/Database/Query.php (1)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Adapter/SQLite.php (3)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2024-2027)
src/Database/Adapter.php (3)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2024-2027)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/MariaDB.php (3)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2024-2027)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Validator/Query/Filter.php (2)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Query.php (1)
  • Query (8-1124)
src/Database/Adapter/SQL.php (1)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Adapter/Postgres.php (5)
src/Database/Query.php (2)
  • Query (8-1124)
  • isObjectAttribute (966-969)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Validator/Structure.php (1)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Database.php (4)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2024-2027)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
tests/e2e/Adapter/Scopes/DocumentTests.php (5)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Database.php (1)
  • Database (37-8180)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2024-2027)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
🪛 GitHub Actions: CodeQL
src/Database/Database.php

[error] 2057-2057: phpstan: Unreachable statement - code above always terminates. (Command: ./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 2G)


[error] 2065-2065: phpstan: Unreachable statement - code above always terminates. (Command: ./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 2G)

tests/e2e/Adapter/Scopes/DocumentTests.php

[error] 6571-6571: phpstan: Unreachable statement - code above always terminates. (in context of class Tests\E2E\Adapter\Base) (Command: ./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 2G)


[error] 7064-7064: phpstan: Unreachable statement - code above always terminates. (in context of class Tests\E2E\Adapter\Base) (Command: ./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 2G)

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

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

(UnusedLocalVariable)


6839-6839: Avoid unused local variables such as '$doc7'. (undefined)

(UnusedLocalVariable)


6853-6853: Avoid unused local variables such as '$doc8'. (undefined)

(UnusedLocalVariable)


6944-6944: Avoid unused local variables such as '$doc11'. (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 (10)
src/Database/Database.php (7)

47-47: Add object type constant — looks good.
Public constant for object attributes is clear and scoped correctly.


1954-1964: Attribute validation for object type — good constraints.
Adapter gating + empty size + non-array are correct.


1980-1980: Error message update includes object type — OK.
Keeps guidance complete for unknown types.


2067-2067: Unknown-type message includes object — OK.
Matches earlier list.


2314-2325: Update-attribute rules for object type — good.
Same constraints as create path and adapter gating. Consistent.


7321-7325: Casting support for object — good fallback.
Decoding JSON string to array when the adapter doesn’t cast types is appropriate.


2052-2065: Fix unreachable code and incorrect array recursion for object/spatial types.

The current code has an early return at line 2036 when $defaultType === 'array', making the case self::TYPE_OBJECT and spatial type cases (lines 2049–2062) unreachable. Additionally, for object and spatial types with array defaults, the code incorrectly recurses into each element, causing validation errors. The proposed diff moves the return inside a conditional that excludes TYPE_OBJECT and spatial types, allowing them to skip element-wise recursion and be validated directly in the switch statement.

Apply the provided diff to resolve both the unreachable code and the incorrect validation logic.

Note: phpstan cannot be run in this environment, but the code inspection confirms the logic issue and the correctness of the proposed fix.

src/Database/Adapter/Postgres.php (3)

1646-1648: Early dispatch for object attributes

Routing object queries here is good. With the above patch, NOT_* will also be handled cleanly; otherwise NOT_* will still throw. Please align both layers (validator and adapter) before merging.


1797-1800: Type mapping: object → JSONB

Correct choice for Postgres. No issues.


1811-1811: Error message updated to include object

Good catch adding TYPE_OBJECT to the exception text.

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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/Database/Database.php (1)

2055-2070: Restore proper control flow in validateDefaultTypes.

case self::TYPE_OBJECT currently hits a var_dump and falls through to default, so every object (and spatial) attribute default now throws Unknown attribute type. Besides killing valid defaults, the var_dump dumps to stdout in production. Drop the debug call and add explicit breaks so known types don’t cascade into the default case.

Apply this diff:

-            case self::TYPE_OBJECT:
-                // Object types expect arrays as default values
-                var_dump($defaultType);
-                if ($defaultType !== 'array') {
-                    throw new DatabaseException('Default value for object type must be an array');
-                }
-                // no break
+            case self::TYPE_OBJECT:
+                // Object types expect arrays as default values
+                if ($defaultType !== 'array') {
+                    throw new DatabaseException('Default value for object type must be an array');
+                }
+                break;
             case self::VAR_POINT:
             case self::VAR_LINESTRING:
             case self::VAR_POLYGON:
                 // Spatial types expect arrays as default values
                 if ($defaultType !== 'array') {
                     throw new DatabaseException('Default value for spatial type ' . $type . ' must be an array');
                 }
-                // no break
+                break;
             default:
                 throw new DatabaseException('Unknown attribute type: ' . $type . '. Must be one of ' . self::VAR_STRING . ', ' . self::VAR_INTEGER . ', ' . self::VAR_FLOAT . ', ' . self::VAR_BOOLEAN . ', ' . self::VAR_DATETIME . ', ' . self::VAR_RELATIONSHIP . ', ' . self::TYPE_OBJECT . ', ' . self::VAR_POINT . ', ' . self::VAR_LINESTRING . ', ' . self::VAR_POLYGON);
♻️ Duplicate comments (1)
src/Database/Adapter/Postgres.php (1)

1571-1624: JSONB query semantics: make EQUAL strict, drop auto-wrap, and guard types

  • EQUAL uses @> for arrays/objects (containment), not equality.
  • CONTAINS auto-wraps values; surprising and leaky.
  • count($value) on non-arrays can warn at runtime.

Apply this refactor for predictable behavior and full parity (adds strict equality, NOT_* support, and input guarding):

-    protected function handleObjectQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string
-    {
-        switch ($query->getMethod()) {
-            case Query::TYPE_EQUAL:
-            case Query::TYPE_NOT_EQUAL: {
-                $isNot = $query->getMethod() === Query::TYPE_NOT_EQUAL;
-                $conditions = [];
-                foreach ($query->getValues() as $key => $value) {
-                    $binds[":{$placeholder}_{$key}"] = json_encode($value);
-                    if (is_array($value)) {
-                        $fragment = "{$alias}.{$attribute} @> :{$placeholder}_{$key}::jsonb";
-                        $conditions[] = $isNot ? "NOT (" . $fragment . ")" : $fragment;
-                    } else {
-                        $fragment = "{$alias}.{$attribute} = :{$placeholder}_{$key}::jsonb";
-                        $conditions[] = $isNot ? "{$alias}.{$attribute} <> :{$placeholder}_{$key}::jsonb" : $fragment;
-                    }
-                }
-                $separator = $isNot ? ' AND ' : ' OR ';
-                return empty($conditions) ? '' : '(' . implode($separator, $conditions) . ')';
-            }
-
-            case Query::TYPE_CONTAINS:
-            case Query::TYPE_NOT_CONTAINS: {
-                $isNot = $query->getMethod() === Query::TYPE_NOT_CONTAINS;
-                $conditions = [];
-                foreach ($query->getValues() as $key => $value) {
-                    if (count($value) === 1) {
-                        $jsonKey = array_key_first($value);
-                        $jsonValue = $value[$jsonKey];
-                        // wrap to represent array; eg: key -> [value]
-                        $value[$jsonKey] = [$jsonValue];
-                    }
-                    $binds[":{$placeholder}_{$key}"] = json_encode($value);
-                    $fragment = "{$alias}.{$attribute} @> :{$placeholder}_{$key}::jsonb";
-                    $conditions[] = $isNot ? "NOT (" . $fragment . ")" : $fragment;
-                }
-                $separator = $isNot ? ' AND ' : ' OR ';
-                return empty($conditions) ? '' : '(' . implode($separator, $conditions) . ')';
-            }
-
-            default:
-                throw new DatabaseException('Query method ' . $query->getMethod() . ' not supported for object attributes');
-        }
-    }
+    protected function handleObjectQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string
+    {
+        $conditions = [];
+        $method = $query->getMethod();
+        $isNot = \in_array($method, [Query::TYPE_NOT_EQUAL, Query::TYPE_NOT_CONTAINS], true);
+
+        foreach ($query->getValues() as $key => $value) {
+            // Expect JSON arrays/objects for object queries
+            if (!\is_array($value)) {
+                throw new DatabaseException('Object queries expect JSON objects/arrays');
+            }
+            $binds[":{$placeholder}_{$key}"] = \json_encode($value);
+            $rhs = ":{$placeholder}_{$key}::jsonb";
+
+            $cond = match ($method) {
+                // Strict JSONB equality/inequality
+                Query::TYPE_EQUAL        => "{$alias}.{$attribute} = {$rhs}",
+                Query::TYPE_NOT_EQUAL    => "NOT ({$alias}.{$attribute} = {$rhs})",
+                // JSONB containment
+                Query::TYPE_CONTAINS     => "{$alias}.{$attribute} @> {$rhs}",
+                Query::TYPE_NOT_CONTAINS => "NOT ({$alias}.{$attribute} @> {$rhs})",
+                default => throw new DatabaseException('Query method ' . $method . ' not supported for object attributes'),
+            };
+            $conditions[] = $cond;
+        }
+
+        $separator = $isNot ? ' AND ' : ' OR ';
+        return empty($conditions) ? '' : '(' . \implode($separator, $conditions) . ')';
+    }
🧹 Nitpick comments (6)
src/Database/Adapter/Postgres.php (1)

870-887: Shared-tables + GIN/GIST: use per-tenant partial indexes

Since _tenant can’t be a GIN/GIST column, add a WHERE clause to scope the index per tenant for planner usability.

         $sql = "CREATE {$sqlType} {$key} ON {$this->getSQLTable($collection)}";
@@
-        $sql .= " ({$attributes});";
+        $whereTenant = '';
+        if ($this->sharedTables && ($type === Database::INDEX_SPATIAL || $type === Database::INDEX_GIN)) {
+            $whereTenant = ' WHERE _tenant = ' . (int)$this->tenant;
+        }
+        $sql .= " ({$attributes}){$whereTenant};";
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (5)

30-30: Prefer assertTrue over assertEquals(true, …)

Use assertTrue for boolean assertions.

-        $this->assertEquals(true, $database->createAttribute($collectionId, 'meta', Database::TYPE_OBJECT, 0, false));
+        $this->assertTrue($database->createAttribute($collectionId, 'meta', Database::TYPE_OBJECT, 0, false));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'data', Database::TYPE_OBJECT, 0, false));
+        $this->assertTrue($database->createAttribute($collectionId, 'data', Database::TYPE_OBJECT, 0, false));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'metaDefaultEmpty', Database::TYPE_OBJECT, 0, false, []));
+        $this->assertTrue($database->createAttribute($collectionId, 'metaDefaultEmpty', Database::TYPE_OBJECT, 0, false, []));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'settings', Database::TYPE_OBJECT, 0, false, $defaultSettings));
+        $this->assertTrue($database->createAttribute($collectionId, 'settings', Database::TYPE_OBJECT, 0, false, $defaultSettings));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'profile', Database::TYPE_OBJECT, 0, true, null));
+        $this->assertTrue($database->createAttribute($collectionId, 'profile', Database::TYPE_OBJECT, 0, true, null));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'profile2', Database::TYPE_OBJECT, 0, false, ['name' => 'anon']));
+        $this->assertTrue($database->createAttribute($collectionId, 'profile2', Database::TYPE_OBJECT, 0, false, ['name' => 'anon']));
-        $this->assertEquals(true, $database->createAttribute($collectionId, 'misc', Database::TYPE_OBJECT, 0, false, null));
+        $this->assertTrue($database->createAttribute($collectionId, 'misc', Database::TYPE_OBJECT, 0, false, null));

Also applies to: 556-556, 848-848, 852-852, 855-855, 858-858, 861-861


80-80: Remove unused local variables flagged by PHPMD

These variables aren’t read; drop the assignments to quiet static analysis without changing behavior.

-        $doc2 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
-        $doc7 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
-        $doc8 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
-        $doc11 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
-        $doc1 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([
-        $doc2 = $database->createDocument($collectionId, new Document([
+        $database->createDocument($collectionId, new Document([

Also applies to: 313-313, 327-327, 430-430, 563-563, 576-576


446-446: Misleading comment

This isn’t a negative test; it expects a match. Tweak comment for clarity.

-        // Test 29: Negative test - partial object match should still work (containment)
+        // Test 29: Partial object match (containment) should succeed

255-270: Duplicate of Test 15; consider making it a true partial-path check

This block repeats the same nested object used in Test 15. If the goal is partial-path containment, reduce the structure or switch to contains() to exercise subset matching.


589-595: Test intent vs. query target

You query data with ['env' => 'production'] but ‘env’ lives under 'config'. The 0-result assertion is fine, but consider clarifying the comment or querying ['config' => ['env' => 'production']] to demonstrate positive equality semantics separately.

📜 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 4483496 and 10cf2bd.

📒 Files selected for processing (8)
  • src/Database/Adapter/Pool.php (1 hunks)
  • src/Database/Adapter/Postgres.php (7 hunks)
  • src/Database/Database.php (14 hunks)
  • src/Database/Validator/Index.php (4 hunks)
  • tests/e2e/Adapter/Base.php (2 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (2 hunks)
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1 hunks)
  • tests/unit/Validator/IndexTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/Adapter/Scopes/DocumentTests.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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
  • tests/e2e/Adapter/Base.php
🧬 Code graph analysis (7)
src/Database/Validator/Index.php (1)
src/Database/Database.php (1)
  • Database (37-8211)
tests/unit/Validator/IndexTest.php (3)
src/Database/Document.php (2)
  • Document (12-470)
  • getAttribute (224-231)
src/Database/Database.php (1)
  • Database (37-8211)
src/Database/Validator/Index.php (3)
  • Index (10-433)
  • isValid (278-317)
  • getDescription (70-73)
src/Database/Database.php (5)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (572-575)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2027-2030)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/Postgres.php (7)
src/Database/Database.php (2)
  • Database (37-8211)
  • count (6989-7038)
src/Database/Adapter.php (2)
  • count (825-825)
  • getSupportForObject (1064-1064)
src/Database/Query.php (2)
  • Query (8-1124)
  • isObjectAttribute (966-969)
src/Database/Adapter/Pool.php (2)
  • count (273-276)
  • getSupportForObject (572-575)
src/Database/Adapter/SQL.php (1)
  • count (2534-2596)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (6)
src/Database/Adapter.php (1)
  • Adapter (16-1325)
src/Database/Database.php (4)
  • Database (37-8211)
  • createAttribute (1695-1750)
  • count (6989-7038)
  • createIndex (3271-3454)
src/Database/Validator/Index.php (1)
  • Index (10-433)
src/Database/Query.php (5)
  • Query (8-1124)
  • equal (435-438)
  • contains (507-510)
  • notEqual (447-450)
  • select (580-583)
src/Database/Adapter/Pool.php (4)
  • getSupportForObject (572-575)
  • createAttribute (168-171)
  • count (273-276)
  • createIndex (213-216)
src/Database/Adapter/Postgres.php (3)
  • getSupportForObject (2027-2030)
  • createAttribute (444-463)
  • createIndex (839-897)
src/Database/Adapter/Pool.php (5)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2027-2030)
src/Database/Adapter.php (1)
  • getSupportForObject (1064-1064)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Mirror.php (1)
  • delegate (88-103)
tests/e2e/Adapter/Base.php (1)
src/Database/Adapter.php (1)
  • Adapter (16-1325)
🪛 GitHub Actions: CodeQL
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php

[error] 841-841: PHPStan: Unreachable statement - code above always terminates. (tests/e2e/Adapter/Scopes/ObjectAttributeTests.php:841)

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

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

(UnusedLocalVariable)


313-313: Avoid unused local variables such as '$doc7'. (undefined)

(UnusedLocalVariable)


327-327: Avoid unused local variables such as '$doc8'. (undefined)

(UnusedLocalVariable)


430-430: Avoid unused local variables such as '$doc11'. (undefined)

(UnusedLocalVariable)


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

(UnusedLocalVariable)


576-576: 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). (10)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (Mirror)
🔇 Additional comments (11)
src/Database/Adapter/Pool.php (1)

571-575: LGTM: delegator added for getSupportForObject()

Matches existing delegation pattern. No issues.

src/Database/Adapter/Postgres.php (3)

1649-1651: LGTM: object-attribute query path integrated

Delegates to handleObjectQueries when attribute type is object.


1800-1815: LGTM: TYPE_OBJECT mapped to JSONB

Consistent with Postgres JSON storage.


2022-2031: LGTM: capability flag returns true

Matches adapter capabilities; Pool and interface aligned.

tests/unit/Validator/IndexTest.php (1)

241-326: LGTM: comprehensive GIN index validator coverage

Covers support gating, single-attr constraint, type check, and order restriction.

src/Database/Validator/Index.php (3)

33-55: LGTM: plumbed objectIndexSupport through constructor

Docblock updated; field assigned.


312-315: LGTM: GIN check included in validation chain

Order is appropriate; returns early on failure.


392-431: LGTM: dedicated GIN rules

Rules match Database::createIndex() errors and new tests.

tests/e2e/Adapter/Base.php (1)

11-11: Verified: Trait gating properly implemented

Confirmed that ObjectAttributeTests trait includes getSupportForObject() checks at multiple test entry points (lines 22, 548, 656, 839), each followed by markTestSkipped() calls. Tests will correctly skip on unsupporting adapters. Code changes approved.

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

21-24: Good skip gating for unsupported adapters

Adapter capability checks are correct and prevent false negatives on non-JSON backends.

Also applies to: 547-551, 655-659


346-356: Query::notContains is available and properly wired for object attributes.

Verification confirms that Query::notContains exists at src/Database/Query.php:519 and is fully implemented for object attributes:

  • Postgres adapter handles JSONB queries with explicit negation logic (wraps conditions with NOT())
  • SQL adapter supports the method via getLikeOperator()
  • Test 21 validates that contains works on the same object structure; Test 23b correctly expects notContains to exclude matching documents

No replacement or alternative is needed.

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

♻️ Duplicate comments (3)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (3)

153-156: Fix notEqual payload: remove extra array nesting.

Query::notEqual expects a single value (object or scalar). The extra wrapping [[...]] produces incorrect semantics.

Apply:

-            Query::notEqual('meta', [['age' => 26]])
+            Query::notEqual('meta', ['age' => 26])

162-169: Fix notEqual payload: remove extra array nesting.

Same issue—remove the outer array wrapper.

Apply:

-            Query::notEqual('meta', [[
-                'user' => [
-                    'info' => [
-                        'country' => 'CA'
-                    ]
-                ]
-            ]])
+            Query::notEqual('meta', [
+                'user' => [
+                    'info' => [
+                        'country' => 'CA'
+                    ]
+                ]
+            ])

478-482: Fix contains check for nested array element.

To assert that meta.matrix contains [4,5,6] as an array element (not individual scalars), wrap it as an array-of-array:

-            Query::contains('meta', [['matrix' => [4, 5, 6]]])
+            Query::contains('meta', [['matrix' => [[4, 5, 6]]]])
🧹 Nitpick comments (1)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1)

589-593: Clarify comment: query doesn't match document structure.

The comment "GIN index doesn't make equal queries work differently" is misleading. The query returns 0 results because it searches for {'env': 'production'} at the top level of data, but the actual structure is nested under data.config.env. Consider clarifying:

-        $this->assertCount(0, $results); // Note: GIN index doesn't make equal queries work differently
+        $this->assertCount(0, $results); // Query structure doesn't match document nesting (env is under config)
📜 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 10cf2bd and 8cd5921.

📒 Files selected for processing (1)
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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
🧬 Code graph analysis (1)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (5)
src/Database/Adapter.php (1)
  • Adapter (16-1325)
src/Database/Database.php (5)
  • Database (37-8211)
  • createAttribute (1695-1750)
  • createDocument (4237-4331)
  • updateDocument (4783-4984)
  • createIndex (3271-3454)
src/Database/Validator/Index.php (1)
  • Index (10-433)
src/Database/Query.php (6)
  • Query (8-1124)
  • equal (435-438)
  • contains (507-510)
  • notEqual (447-450)
  • select (580-583)
  • notContains (519-522)
src/Database/Adapter/Postgres.php (5)
  • getSupportForObject (2027-2030)
  • createAttribute (444-463)
  • createDocument (956-1056)
  • updateDocument (1070-1270)
  • createIndex (839-897)
🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php

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

(UnusedLocalVariable)


313-313: Avoid unused local variables such as '$doc7'. (undefined)

(UnusedLocalVariable)


327-327: Avoid unused local variables such as '$doc8'. (undefined)

(UnusedLocalVariable)


430-430: Avoid unused local variables such as '$doc11'. (undefined)

(UnusedLocalVariable)


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

(UnusedLocalVariable)


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

(UnusedLocalVariable)

🔇 Additional comments (10)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (10)

22-24: LGTM: Clean adapter capability check.

The skip logic correctly gates the test without the unreachable return that was flagged in other methods.


16-540: Excellent comprehensive test coverage.

This test method thoroughly exercises object attribute functionality including CRUD operations, nested structures, special characters, mixed types, null/empty values, and edge cases. The test progression is logical and well-commented.


548-550: LGTM: Correct skip logic.

The adapter support check is properly implemented without unreachable code.


609-644: Excellent error case coverage for GIN indexes.

The negative tests properly validate all three GIN index constraints: object-type-only, single-attribute, and no explicit orders. Exception handling is thorough with appropriate assertions on exception type and message content.


656-658: LGTM: Clean skip logic.

Adapter support check is properly implemented.


666-706: Correct type validation tests.

The three tests properly validate that non-array values (string, integer, boolean) are rejected for object attributes with StructureException. The skip guard at line 656 ensures these only run on adapters with object support.


764-795: Good edge case: large structure test.

Testing a 50-element nested structure validates that the implementation handles realistic large JSONB documents. The query verification on line 783 ensures retrieval works correctly within large structures.


839-841: LGTM: Unreachable return fixed.

The previous issue with an unreachable return after markTestSkipped has been corrected. The skip logic is now clean.


862-874: Correct validation of required attribute.

The test properly validates that omitting a required object attribute (profile) throws StructureException. The exception handling and assertions are appropriate.


876-908: Thorough default value validation.

The test correctly validates all default scenarios: empty array, nested object, explicit null, and required vs non-required behavior. The query test at lines 903-907 confirms defaults are properly stored and queryable.

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 (1)
src/Database/Database.php (1)

2599-2607: Pass the new “object support” flag to IndexValidator.

IndexValidator now expects the adapter’s object-support flag. This call site still uses the old signature, so updateAttribute() will trigger an ArgumentCountError as soon as it tries to validate indexes. Add the missing $this->adapter->getSupportForObject() argument here to keep attribute updates working.

                     $validator = new IndexValidator(
                         $attributes,
                         $this->adapter->getMaxIndexLength(),
                         $this->adapter->getInternalIndexesKeys(),
                         $this->adapter->getSupportForIndexArray(),
                         $this->adapter->getSupportForSpatialIndexNull(),
                         $this->adapter->getSupportForSpatialIndexOrder(),
-                        $this->adapter->getSupportForVectors(),
+                        $this->adapter->getSupportForVectors(),
+                        $this->adapter->getSupportForObject(),
                     );
♻️ Duplicate comments (1)
src/Database/Adapter/Postgres.php (1)

1607-1618: Address the unresolved issues from previous review.

The past review comment on lines 1594-1648 identified critical semantic issues that remain unaddressed:

  1. EQUAL (line 1613): Uses @> (containment) instead of = (strict equality). This means Query::equal('meta', ['status' => 'active']) will match documents where meta is {"status": "active", "other": "field"} instead of requiring an exact match. This is a semantic mismatch with the query name "equal".

  2. CONTAINS auto-wrapping (lines 1633-1635): Still wraps scalar values in arrays automatically. The previous review recommended removing this auto-wrapping so callers explicitly pass the JSON shape they want to contain.

These issues affect query semantics and should be resolved before merge.

Also applies to: 1620-1643

📜 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 117af69 and a92fd41.

📒 Files selected for processing (12)
  • src/Database/Adapter.php (1 hunks)
  • src/Database/Adapter/MariaDB.php (1 hunks)
  • src/Database/Adapter/Pool.php (1 hunks)
  • src/Database/Adapter/Postgres.php (7 hunks)
  • src/Database/Adapter/SQL.php (1 hunks)
  • src/Database/Database.php (12 hunks)
  • src/Database/Query.php (1 hunks)
  • src/Database/Validator/Index.php (4 hunks)
  • src/Database/Validator/Query/Filter.php (2 hunks)
  • src/Database/Validator/Structure.php (1 hunks)
  • tests/e2e/Adapter/Base.php (2 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/Database/Adapter/Pool.php
  • src/Database/Adapter.php
  • src/Database/Adapter/SQL.php
  • src/Database/Validator/Structure.php
  • src/Database/Query.php
  • src/Database/Adapter/MariaDB.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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/Base.php
🧬 Code graph analysis (4)
src/Database/Validator/Query/Filter.php (2)
src/Database/Database.php (1)
  • Database (37-8344)
src/Database/Query.php (1)
  • Query (8-1177)
src/Database/Adapter/Postgres.php (6)
src/Database/Database.php (2)
  • Database (37-8344)
  • count (7123-7172)
src/Database/Adapter.php (2)
  • count (825-825)
  • getSupportForObject (1071-1071)
src/Database/Query.php (2)
  • Query (8-1177)
  • isObjectAttribute (983-986)
src/Database/Adapter/Pool.php (2)
  • count (273-276)
  • getSupportForObject (577-580)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Database.php (5)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2097-2100)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (577-580)
src/Database/Adapter.php (1)
  • getSupportForObject (1071-1071)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Validator/Index.php (2)
src/Database/Document.php (2)
  • Document (12-470)
  • getAttribute (224-231)
src/Database/Database.php (1)
  • Database (37-8344)
⏰ 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). (11)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Unit Test
🔇 Additional comments (10)
tests/e2e/Adapter/Base.php (1)

11-11: LGTM! Clean addition of object attribute test coverage.

The import and trait usage are correctly integrated into the Base test class. The import follows alphabetical ordering, and the trait usage is appropriately positioned.

Also applies to: 30-30

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

150-158: LGTM! Clean validation for object type queries.

The array-only requirement is now enforced consistently across validator and adapter layers.


232-236: LGTM! Error message accurately reflects object type support.

The condition and message correctly allow contains/notContains on object attributes alongside strings and spatial types.

src/Database/Adapter/Postgres.php (4)

889-890: LGTM! GIN index support properly integrated.

The index creation handles GIN consistently with other special index types (spatial, HNSW), includes appropriate tenant column handling for shared tables, and uses the correct USING GIN clause for JSONB indexes.

Also applies to: 896-896, 909-909


1673-1675: LGTM! Routing logic is correct.

The placement and pattern match the spatial query handling above. Once the issues in handleObjectQueries are resolved, this integration point will work correctly.


1858-1860: LGTM! SQL type mapping is correct.

JSONB is the appropriate PostgreSQL type for object attributes, and the error message accurately reflects the expanded type support.

Also applies to: 1874-1874


2092-2100: LGTM! Capability method correctly implemented.

Returns true to indicate Postgres supports object (JSONB) attributes, following the established pattern for adapter capability flags.

src/Database/Validator/Index.php (3)

27-38: LGTM! Constructor parameter follows established pattern.

The objectIndexSupport flag is wired consistently with other adapter capability parameters and defaults safely to false.


411-413: LGTM! Validation flow correctly extended.

The checkGinIndex call is properly integrated into the validation chain.


442-482: LGTM! GIN index validation is thorough and consistent.

The implementation correctly mirrors the patterns from checkSpatialIndex and checkVectorIndex:

  • Single attribute requirement
  • Type checking (must be TYPE_OBJECT)
  • Order restrictions
  • Adapter support flag validation

All error messages are clear and actionable.

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/Validator/Index.php (1)

99-137: The checkGinIndex method is never called in isValid.

The new checkGinIndex method (lines 535-575) is defined but never invoked in the validation flow. The isValid method calls all other check methods but is missing a call to checkGinIndex, rendering GIN index validation completely non-functional.

Add the missing call:

     if (!$this->checkIdenticalIndexes($value)) {
         return false;
     }
+    if (!$this->checkGinIndex($value)) {
+        return false;
+    }
     return true;
 }
src/Database/Database.php (1)

2487-2502: Bug: malformed supportedTypes list (string concatenation inside array).

This turns two entries into one string ("relationship, object"), corrupting the list and error text. Fix by making TYPE_OBJECT its own element (conditioned on adapter support).

-                    $supportedTypes = [
+                    $supportedTypes = [
                         self::VAR_STRING,
                         self::VAR_INTEGER,
                         self::VAR_FLOAT,
                         self::VAR_BOOLEAN,
                         self::VAR_DATETIME,
-                        self::VAR_RELATIONSHIP . ', ' . self::TYPE_OBJECT
+                        self::VAR_RELATIONSHIP,
                     ];
+                    if ($this->adapter->getSupportForObject()) {
+                        $supportedTypes[] = self::TYPE_OBJECT;
+                    }
                     if ($this->adapter->getSupportForVectors()) {
                         $supportedTypes[] = self::VAR_VECTOR;
                     }
                     if ($this->adapter->getSupportForSpatialAttributes()) {
                         \array_push($supportedTypes, ...self::SPATIAL_TYPES);
                     }
                     throw new DatabaseException('Unknown attribute type: ' . $type . '. Must be one of ' . implode(', ', $supportedTypes));
♻️ Duplicate comments (2)
tests/unit/Validator/IndexTest.php (1)

323-323: Missing required indexes parameter in Index validator constructor.

Same issue as line 277 - the indexes parameter is missing from the constructor call.

Apply the fix:

-        $validatorNoSupport = new Index($collection->getAttribute('attributes'), 768, [], false, false, false, false, false);
+        $validatorNoSupport = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768, [], false, false, false, false, true, true, true, false);
src/Database/Validator/Structure.php (1)

361-368: Array-of-objects validation is bypassed by continue 2.

The continue 2 at line 368 skips the generic array handling logic (lines 396-417), so attributes with array: true and type TYPE_OBJECT are not validated properly. Specifically:

  • When array: true, the value should be validated as a list of objects (each element must be an array)
  • The code should enforce that the value is a proper list using array_is_list()
  • Currently, a single object or array-of-objects are treated identically

This is the same issue raised in the past review comment.

Apply this approach to validate both shapes:

 case Database::TYPE_OBJECT:
-    // For JSONB/object types, just validate it's an array (associative or list)
-    if (!is_array($value)) {
-        $this->message = 'Attribute "'.$key.'" has invalid type. Value must be an array for object type';
-        return false;
-    }
-    // No additional validators needed - JSONB accepts any valid array structure
-    continue 2; // Skip to next attribute
+    if ($array) {
+        // Expect a list of objects (each item must be an array)
+        if (!\is_array($value) || !\array_is_list($value)) {
+            $this->message = 'Attribute "'.$key.'" must be an array of objects';
+            return false;
+        }
+        foreach ($value as $i => $child) {
+            if (!\is_array($child)) {
+                $this->message = 'Attribute "'.$key.'[\''.$i.'\']" must be an object';
+                return false;
+            }
+        }
+        continue 2;
+    }
+    // Single JSON object (associative or list) represented as PHP array
+    if (!\is_array($value)) {
+        $this->message = 'Attribute "'.$key.'" must be an object';
+        return false;
+    }
+    continue 2;
🧹 Nitpick comments (4)
src/Database/Database.php (4)

2018-2028: Object attribute validation: OK, but tighten boolean checks.

Logic is correct. For clarity/readability, prefer direct boolean checks over empty():

-                if (!empty($size)) {
+                if ($size !== 0) {
                     throw new DatabaseException('Size must be empty for object attributes');
                 }
-                if (!empty($array)) {
+                if ($array) {
                     throw new DatabaseException('Object attributes cannot be arrays');
                 }
+                // Optional: clear error if default is provided in a wrong type
+                if ($default !== null && !\is_array($default)) {
+                    throw new DatabaseException('Default value for object type must be an array');
+                }

2434-2444: Update-attribute object validation: OK, same optional boolean/readability tweaks.

Mirror the create case; minor boolean style nit as above.

-                    if (!empty($size)) {
+                    if ($size !== 0) {
                         throw new DatabaseException('Size must be empty for object attributes');
                     }
-                    if (!empty($array)) {
+                    if ($array) {
                         throw new DatabaseException('Object attributes cannot be arrays');
                     }

3531-3550: GIN index validation: add a lengths check.

Orders are disallowed; lengths should be too. GIN ignores column length and allowing it can confuse users.

             if (!empty($orders)) {
                 throw new IndexException('GIN indexes do not support explicit orders. Remove the orders to create this index.');
             }
+            if (!empty($lengths)) {
+                throw new IndexException('GIN indexes do not support lengths. Remove the lengths to create this index.');
+            }

7564-7568: Guard JSON decode for object casting.

Invalid JSON will become null. Preserve original on decode failure.

-                        if (is_string($node)) {
-                            $node = json_decode($node, true);
-                        }
+                        if (is_string($node)) {
+                            $decoded = json_decode($node, true);
+                            if (json_last_error() === JSON_ERROR_NONE) {
+                                $node = $decoded;
+                            }
+                        }
📜 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 6ba8558 and 49139d8.

📒 Files selected for processing (11)
  • src/Database/Adapter.php (1 hunks)
  • src/Database/Adapter/Pool.php (1 hunks)
  • src/Database/Adapter/SQL.php (1 hunks)
  • src/Database/Database.php (12 hunks)
  • src/Database/Validator/Index.php (3 hunks)
  • src/Database/Validator/Query/Filter.php (2 hunks)
  • src/Database/Validator/Structure.php (1 hunks)
  • tests/e2e/Adapter/Base.php (2 hunks)
  • tests/e2e/Adapter/Scopes/AttributeTests.php (1 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (2 hunks)
  • tests/unit/Validator/IndexTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Database/Adapter.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/e2e/Adapter/Scopes/AttributeTests.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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/Base.php
🧬 Code graph analysis (8)
src/Database/Validator/Query/Filter.php (2)
src/Database/Database.php (1)
  • Database (37-8426)
src/Database/Query.php (1)
  • Query (8-1177)
tests/unit/Validator/IndexTest.php (2)
src/Database/Database.php (1)
  • Database (37-8426)
src/Database/Validator/Index.php (3)
  • Index (10-576)
  • isValid (99-138)
  • getDescription (74-77)
src/Database/Validator/Structure.php (1)
src/Database/Database.php (1)
  • Database (37-8426)
tests/e2e/Adapter/Base.php (1)
src/Database/Adapter.php (1)
  • Adapter (16-1408)
src/Database/Database.php (5)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (583-586)
src/Database/Adapter.php (1)
  • getSupportForObject (1078-1078)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2097-2100)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQL.php (1)
src/Database/Database.php (1)
  • Database (37-8426)
src/Database/Validator/Index.php (2)
src/Database/Database.php (2)
  • Database (37-8426)
  • count (7193-7244)
src/Database/Adapter.php (1)
  • count (825-825)
src/Database/Adapter/Pool.php (5)
src/Database/Adapter.php (1)
  • getSupportForObject (1078-1078)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2097-2100)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Mirror.php (1)
  • delegate (88-103)
🔇 Additional comments (14)
tests/e2e/Adapter/Base.php (1)

11-11: LGTM!

The ObjectAttributeTests trait import and usage cleanly extends the test coverage to include object attribute scenarios.

Also applies to: 32-32

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

1125-1132: LGTM!

The TYPE_OBJECT case correctly accounts for JSONB/JSON external storage by adding only the 20-byte pointer contribution to row size, consistent with the existing pattern for other externally-stored types.

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

583-586: LGTM!

The getSupportForObject method correctly delegates to the underlying adapter using the established pattern, consistent with other getSupportFor* methods in the class.

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

165-172: LGTM!

The TYPE_OBJECT case correctly enforces that query values must be arrays (representing JSON objects) and bypasses further validation, which is appropriate for JSONB attributes that accept any valid array structure.


247-251: LGTM!

The contains/notContains validation correctly includes TYPE_OBJECT as an allowed attribute type, with an appropriate error message update.

src/Database/Validator/Index.php (2)

31-31: LGTM!

The objectIndexSupport parameter is correctly added to the constructor signature and documentation, following the established pattern for other support flags.

Also applies to: 46-46


535-575: LGTM with caveat!

The checkGinIndex implementation correctly validates GIN index constraints:

  • Adapter support check via objectIndexSupport flag
  • Single attribute requirement
  • No explicit orders allowed
  • Attribute must be TYPE_OBJECT

However, this method is not called in isValid (see separate comment on lines 99-137).

src/Database/Database.php (7)

50-52: Constant addition looks good.

TYPE_OBJECT name and placement align with existing conventions.


76-76: New index type constant is fine.

INDEX_GIN added consistently with other index constants.


1462-1475: Validator wiring LGTM.

Passing getSupportForObject() into IndexValidator is correct and consistent with other capability flags.


2135-2143: Default-type recursion change for object is correct.

Allowing arrays for object defaults and skipping per-item recursion is appropriate.


3492-3497: Capability gate for GIN indexes is reasonable.

Message is concise; gating via getSupportForObject() matches adapter contract.


3573-3576: Passing object-support into IndexValidator here also looks correct.


2073-2088: Review comment addresses only one of three identical code blocks; verify completeness of changes.

The review correctly identifies that TYPE_OBJECT should be included in the error message when supported, but there are three separate occurrences of this supportedTypes pattern at lines 2087, 2180, and 2501. Additionally, line 2501 in updateAttribute() already references TYPE_OBJECT, but via string concatenation (self::VAR_RELATIONSHIP . ', ' . self::TYPE_OBJECT), which is inconsistent with the pattern of other types.

Verify:

  • Whether all three methods need the same TYPE_OBJECT handling
  • Whether line 2501's string concatenation approach is intentional or a pre-existing bug requiring separate correction
  • Ensure consistency across all occurrences after implementing this fix

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 (3)
src/Database/Database.php (3)

2118-2183: Add explicit default validation for TYPE_OBJECT.

Non-array defaults should error with a clear message; currently they fall into “Unknown attribute type”.

@@ protected function validateDefaultTypes(string $type, mixed $default): void
         switch ($type) {
             case self::VAR_STRING:
             case self::VAR_INTEGER:
             case self::VAR_FLOAT:
             case self::VAR_BOOLEAN:
                 if ($type !== $defaultType) {
                     throw new DatabaseException('Default value ' . $default . ' does not match given type ' . $type);
                 }
                 break;
             case self::VAR_DATETIME:
                 if ($defaultType !== self::VAR_STRING) {
                     throw new DatabaseException('Default value ' . $default . ' does not match given type ' . $type);
                 }
                 break;
+            case self::TYPE_OBJECT:
+                if ($defaultType !== 'array') {
+                    throw new DatabaseException('Default value for object type must be an array');
+                }
+                break;
             case self::VAR_VECTOR:
                 // When validating individual vector components (from recursion), they should be numeric
                 if ($defaultType !== 'double' && $defaultType !== 'integer') {
                     throw new DatabaseException('Vector components must be numeric values (float or integer)');
                 }
                 break;

2488-2503: Fix supported types array concatenation (error message).

Concatenating VAR_RELATIONSHIP and TYPE_OBJECT into one element breaks message formatting.

-                        self::VAR_DATETIME,
-                        self::VAR_RELATIONSHIP . ', ' . self::TYPE_OBJECT
+                        self::VAR_DATETIME,
+                        self::VAR_RELATIONSHIP,
+                        self::TYPE_OBJECT

1387-1397: Auto-wire JSON filter for object attributes to ensure consistent encode/decode behavior.

Object attributes require the json filter for proper serialization/deserialization, but unlike SPATIAL_TYPES and VAR_VECTOR, the code does not automatically apply it. This creates an inconsistency: developers must manually add the json filter to object attributes, whereas spatial and vector types are automatically filtered in createCollection(), createAttribute(), and getRequiredFilters().

Apply the proposed changes to all three locations to automatically wire the json filter when TYPE_OBJECT is used:

@@ public function createCollection(...
-        foreach ($attributes as &$attribute) {
+        foreach ($attributes as &$attribute) {
             if (in_array($attribute['type'], Database::SPATIAL_TYPES) || $attribute['type'] === Database::VAR_VECTOR) {
                 $existingFilters = $attribute['filters'] ?? [];
                 if (!is_array($existingFilters)) {
                     $existingFilters = [$existingFilters];
                 }
                 $attribute['filters'] = array_values(
                     array_unique(array_merge($existingFilters, [$attribute['type']]))
                 );
             }
+            if (($attribute['type'] ?? null) === Database::TYPE_OBJECT) {
+                $existingFilters = $attribute['filters'] ?? [];
+                if (!is_array($existingFilters)) {
+                    $existingFilters = [$existingFilters];
+                }
+                $attribute['filters'] = array_values(array_unique(array_merge($existingFilters, ['json'])));
+            }
         }
@@ public function createAttribute(...
-        if ($type === Database::VAR_VECTOR) {
+        if ($type === Database::VAR_VECTOR) {
             $filters[] = $type;
             $filters = array_unique($filters);
         }
+        if ($type === Database::TYPE_OBJECT) {
+            $filters[] = 'json';
+            $filters = array_unique($filters);
+        }
@@ protected function getRequiredFilters(?string $type): array
-        return match ($type) {
-            self::VAR_DATETIME => ['datetime'],
-            default => [],
-        };
+        return match ($type) {
+            self::VAR_DATETIME => ['datetime'],
+            self::TYPE_OBJECT => ['json'],
+            default => [],
+        };
♻️ Duplicate comments (3)
src/Database/Adapter/Postgres.php (1)

1594-1648: ** EQUAL and CONTAINS semantics still need fixes per prior review.**

The past review comment on these lines identified critical issues with JSONB query semantics:

  1. EQUAL (lines 1607-1618) still uses @> (containment) instead of strict equality (=): This means Query::equal('meta', [['age' => 25]]) matches any document where meta contains {"age": 25}, not just documents where meta equals that exact object. This is incorrect semantics for EQUAL.

  2. Auto-wrapping in CONTAINS (lines 1629-1635) remains: The past comment recommended removing this auto-wrapping logic to keep the adapter strict and predictable—callers should pass the exact JSON shape they want to contain.

Progress made: NOT_EQUAL and NOT_CONTAINS were added (good!), and the separator logic for NOT_* uses AND (correct).

Still needed: Apply the past review's suggested diff to fix EQUAL to use strict = equality and remove the auto-wrapping in CONTAINS.

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

153-169: ** Fix notEqual query payload shape.**

Per the past review comment, Query::notEqual expects a single value, not an array-of-arrays. The current usage wraps the object in an extra array:

Query::notEqual('meta', [['age' => 26]])  // current - double-wrapped

Should be:

Query::notEqual('meta', ['age' => 26])  // single object

Same issue on lines 162-169 with the nested object. Apply the fix from the past review comment.


872-873: ** Fix misleading comment.**

The comment states "Required with default" but the attribute is created with required = false (fifth parameter). Update the comment to accurately reflect that the attribute is optional:

-        // 4) Required with default (should auto-populate)
+        // 4) Optional with default (should auto-populate when omitted)
         $this->assertEquals(true, $database->createAttribute($collectionId, 'profile2', Database::TYPE_OBJECT, 0, false, ['name' => 'anon']));
🧹 Nitpick comments (1)
src/Database/Database.php (1)

7564-7569: Object decode path: consider guarding invalid JSON.

If json_decode returns null (invalid JSON), keep original value to avoid silent nulling.

-                        if (is_string($node)) {
-                            $node = json_decode($node, true);
-                        }
+                        if (is_string($node)) {
+                            $decoded = json_decode($node, true);
+                            if (json_last_error() === JSON_ERROR_NONE) {
+                                $node = $decoded;
+                            }
+                        }
📜 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 739a4c3 and 2cb3d98.

📒 Files selected for processing (5)
  • src/Database/Adapter/Postgres.php (7 hunks)
  • src/Database/Database.php (12 hunks)
  • src/Database/Validator/Index.php (4 hunks)
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1 hunks)
  • tests/unit/Validator/IndexTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/Validator/IndexTest.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
PR: utopia-php/database#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
🧬 Code graph analysis (4)
src/Database/Validator/Index.php (1)
src/Database/Database.php (1)
  • Database (37-8427)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (7)
src/Database/Adapter.php (1)
  • Adapter (16-1408)
src/Database/Database.php (5)
  • Database (37-8427)
  • createAttribute (1754-1813)
  • createDocument (4392-4489)
  • find (6957-7097)
  • createIndex (3435-3606)
src/Database/Validator/Index.php (1)
  • Index (10-579)
src/Database/Validator/Structure.php (1)
  • Structure (18-454)
src/Database/Query.php (5)
  • Query (8-1177)
  • equal (452-455)
  • contains (524-527)
  • notEqual (464-467)
  • select (597-600)
src/Database/Adapter/Postgres.php (4)
  • getSupportForObject (2097-2100)
  • createAttribute (442-471)
  • createDocument (979-1079)
  • createIndex (863-920)
src/Database/Adapter/Pool.php (5)
  • getSupportForObject (583-586)
  • createAttribute (168-171)
  • createDocument (228-231)
  • find (263-266)
  • createIndex (213-216)
src/Database/Adapter/Postgres.php (8)
src/Database/Database.php (2)
  • Database (37-8427)
  • count (7194-7245)
src/Database/Query.php (2)
  • Query (8-1177)
  • isObjectAttribute (983-986)
src/Database/Adapter/Pool.php (2)
  • count (273-276)
  • getSupportForObject (583-586)
src/Database/Adapter/Mongo.php (2)
  • count (2093-2180)
  • getSupportForObject (2791-2794)
src/Database/Adapter.php (2)
  • count (825-825)
  • getSupportForObject (1078-1078)
src/Database/Adapter/SQL.php (1)
  • count (2660-2722)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Database.php (6)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2097-2100)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (583-586)
src/Database/Adapter/Mongo.php (1)
  • getSupportForObject (2791-2794)
src/Database/Adapter.php (1)
  • getSupportForObject (1078-1078)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php

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

(UnusedLocalVariable)


313-313: Avoid unused local variables such as '$doc7'. (undefined)

(UnusedLocalVariable)


327-327: Avoid unused local variables such as '$doc8'. (undefined)

(UnusedLocalVariable)


430-430: Avoid unused local variables such as '$doc11'. (undefined)

(UnusedLocalVariable)


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

(UnusedLocalVariable)


576-576: 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). (14)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (SharedTables/MongoDB)
  • GitHub Check: Adapter Tests (Schemaless/MongoDB)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (MongoDB)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Unit Test
🔇 Additional comments (11)
src/Database/Adapter/Postgres.php (4)

858-860: LGTM: TYPE_OBJECT correctly maps to JSONB.

The mapping of Database::TYPE_OBJECT to JSONB is appropriate for Postgres.


889-890: LGTM: Index_Object correctly mapped to GIN index.

The Index_Object type is correctly mapped to use USING GIN, which is the appropriate index type for JSONB columns in Postgres.

Also applies to: 909-909


1673-1675: LGTM: Object query routing is correct.

The routing of object attribute queries through handleObjectQueries integrates properly with the existing query flow.


2092-2100: LGTM: Postgres adapter correctly reports object support.

Returning true for getSupportForObject() is correct for Postgres with JSONB support.

src/Database/Validator/Index.php (3)

31-32: LGTM: Object index support flag properly integrated.

The supportForObjectIndexes parameter follows the established pattern for adapter capability flags and correctly defaults to false.

Also applies to: 46-47


137-139: LGTM: Object index validation properly integrated into validation flow.

The checkObjectIndexes call is correctly placed in the validation chain.


538-578: LGTM: Object index validation logic is sound.

The validation correctly enforces:

  • Object indexes require adapter support
  • Exactly one attribute allowed
  • No explicit orders permitted
  • Target attribute must be of TYPE_OBJECT

This follows the same pattern as spatial and vector index validation.

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

16-540: LGTM: Comprehensive test coverage for object attributes.

The testObjectAttribute method provides excellent coverage:

  • CRUD operations with nested objects
  • Query variations (equal, contains, notEqual, notContains, select)
  • Edge cases (null, empty, deeply nested, mixed types, special characters)
  • Complex structures (arrays of objects, nested arrays)

The test assertions are thorough and the document IDs are descriptive.


542-648: LGTM: GIN index validation is thorough.

The testObjectAttributeGinIndex method correctly validates:

  • Successful creation and usage of object indexes
  • Error handling for invalid configurations (non-object attribute, multiple attributes, explicit orders)

The negative test cases with proper exception assertions are particularly valuable.


650-847: LGTM: Invalid cases and edge cases well covered.

The testObjectAttributeInvalidCases method provides strong validation coverage:

  • Type checking (string, integer, boolean all correctly rejected)
  • Non-matching queries return empty results as expected
  • Large structures (50 keys) exercise scalability
  • Key ordering and complex nested structures

The test balances negative cases with valid follow-up queries to ensure the adapter remains functional.


849-927: LGTM: Default value behavior properly tested.

The testObjectAttributeDefaults method validates:

  • Empty object defaults
  • Nested object defaults
  • Required attributes without defaults (fail correctly)
  • Optional attributes with defaults (auto-populate)
  • Explicit null defaults

The combination of positive tests (defaults work) and negative tests (required fails when missing) provides good coverage of the defaulting logic.

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: 3

Caution

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

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

2488-2503: Broken supportedTypes composition (concatenation inside array).

This creates one combined element like "relationship, object" instead of two entries, degrading the error message and any consumer relying on this list.

Apply this fix:

@@
-                    $supportedTypes = [
-                        self::VAR_STRING,
-                        self::VAR_INTEGER,
-                        self::VAR_FLOAT,
-                        self::VAR_BOOLEAN,
-                        self::VAR_DATETIME,
-                        self::VAR_RELATIONSHIP . ', ' . self::VAR_OBJECT
-                    ];
+                    $supportedTypes = [
+                        self::VAR_STRING,
+                        self::VAR_INTEGER,
+                        self::VAR_FLOAT,
+                        self::VAR_BOOLEAN,
+                        self::VAR_DATETIME,
+                        self::VAR_RELATIONSHIP,
+                        self::VAR_OBJECT,
+                    ];

2526-2536: Fix typo in setAttribute call.

The method call at line 2527 uses incorrect casing. Apply this fix:

-                ->setattribute('key', $newKey ?? $id)
+                ->setAttribute('key', $newKey ?? $id)

This is the only occurrence of this typo in the codebase.

♻️ Duplicate comments (5)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (3)

153-156: Verify Query::notEqual payload shape.

The previous review flagged this payload shape as potentially incorrect. The double-nested array [['age' => 26]] may not match the expected semantics for Query::notEqual on object attributes.

Please verify that the payload shape matches the query semantics for JSONB containment checks.


162-169: Verify Query::notEqual payload shape for nested object.

Similar to the previous comment, the previous review flagged this nested structure as potentially having incorrect array nesting.

Please verify that the payload shape matches the expected query semantics.


872-873: Fix misleading comment - attribute is not required.

The comment states "Required with default" but line 873 creates the attribute with required = false (the 5th parameter).

Apply this diff:

-        // 4) Required with default (should auto-populate)
+        // 4) Optional with default (should auto-populate when omitted)
         $this->assertEquals(true, $database->createAttribute($collectionId, 'profile2', Database::VAR_OBJECT, 0, false, ['name' => 'anon']));
src/Database/Validator/Structure.php (1)

361-368: Array-of-objects validation is still bypassed (control-flow skip).

The continue 2 at line 368 short-circuits before the generic array handler (lines 396–417) runs, so attributes declared with array: true (array of objects) are never validated element-by-element. Single vs. array-of-objects semantics remain unenforced.

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

1607-1618: EQUAL semantics: @> is containment, not strict equality.

Line 1613 uses the containment operator (@>), which returns true for superset matches. For example, {"a":1,"b":2} @> {"a":1} is true, but that's not strict equality.

Apply this diff for strict equality:

             case Query::TYPE_EQUAL:
-            case Query::TYPE_NOT_EQUAL: {
-                $isNot = $query->getMethod() === Query::TYPE_NOT_EQUAL;
-                $conditions = [];
-                foreach ($query->getValues() as $key => $value) {
-                    $binds[":{$placeholder}_{$key}"] = json_encode($value);
-                    $fragment = "{$alias}.{$attribute} @> :{$placeholder}_{$key}::jsonb";
-                    $conditions[] = $isNot ? "NOT (" . $fragment . ")" : $fragment;
-                }
-                $separator = $isNot ? ' AND ' : ' OR ';
-                return empty($conditions) ? '' : '(' . implode($separator, $conditions) . ')';
-            }
+                $conditions = [];
+                foreach ($query->getValues() as $key => $value) {
+                    $binds[":{$placeholder}_{$key}"] = json_encode($value);
+                    $conditions[] = "{$alias}.{$attribute} = :{$placeholder}_{$key}::jsonb";
+                }
+                return empty($conditions) ? '' : '(' . implode(' OR ', $conditions) . ')';
+
+            case Query::TYPE_NOT_EQUAL:
+                $conditions = [];
+                foreach ($query->getValues() as $key => $value) {
+                    $binds[":{$placeholder}_{$key}"] = json_encode($value);
+                    $conditions[] = "NOT ({$alias}.{$attribute} = :{$placeholder}_{$key}::jsonb)";
+                }
+                return empty($conditions) ? '' : '(' . implode(' AND ', $conditions) . ')';
🧹 Nitpick comments (5)
src/Database/Database.php (3)

2136-2144: Default validation: skip recursion for object arrays — LGTM with a small enhancement

Logic correctly treats object defaults as arrays. Consider adding an explicit case to error if a non‑array default is provided for object, for clearer messages.

Apply this focused enhancement:

@@
-        if ($defaultType === 'array') {
+        if ($defaultType === 'array') {
             // Spatial types require the array itself
             if (!in_array($type, Database::SPATIAL_TYPES) && $type != Database::VAR_OBJECT) {
                 foreach ($default as $value) {
                     $this->validateDefaultTypes($type, $value);
                 }
             }
             return;
         }
 
         switch ($type) {
+            case self::VAR_OBJECT:
+                // Object defaults must be arrays (associative or list)
+                throw new DatabaseException('Default value for object type must be an array');

2074-2089: Optional: include object in “Unknown attribute type” list when supported.

Improves diagnostic accuracy for adapters that support objects.

Apply:

@@
-                if ($this->adapter->getSupportForVectors()) {
+                if ($this->adapter->getSupportForVectors()) {
                     $supportedTypes[] = self::VAR_VECTOR;
                 }
                 if ($this->adapter->getSupportForSpatialAttributes()) {
                     \array_push($supportedTypes, ...self::SPATIAL_TYPES);
                 }
+                if ($this->adapter->getSupportForObject()) {
+                    $supportedTypes[] = self::VAR_OBJECT;
+                }

2167-2182: Optional: include object in default-type “Unknown attribute type” list.

Same rationale for clearer errors during default validation.

@@
-                if ($this->adapter->getSupportForVectors()) {
+                if ($this->adapter->getSupportForVectors()) {
                     $supportedTypes[] = self::VAR_VECTOR;
                 }
                 if ($this->adapter->getSupportForSpatialAttributes()) {
                     \array_push($supportedTypes, ...self::SPATIAL_TYPES);
                 }
+                if ($this->adapter->getSupportForObject()) {
+                    $supportedTypes[] = self::VAR_OBJECT;
+                }
tests/unit/Validator/IndexTest.php (1)

323-323: Use named parameter for clarity.

Line 323 passes 9 positional parameters, setting supportForAttributes=false (9th param). The 12th parameter supportForObjectIndexes defaults to false, which happens to be correct, but the code is fragile and obscures intent.

Apply this diff for maintainability:

-        $validatorNoSupport = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes', []), 768, [], false, false, false, false, false);
+        $validatorNoSupport = new Index(
+            $collection->getAttribute('attributes'),
+            $collection->getAttribute('indexes', []),
+            768,
+            supportForObjectIndexes: false
+        );
src/Database/Adapter/Postgres.php (1)

1625-1636: Auto-wrapping in CONTAINS queries obscures intent and limits flexibility.

Lines 1629–1634 automatically wrap scalar values in arrays (e.g., "react" becomes ["react"]), which:

  • Prevents exact JSON-shape queries.
  • Forces array-element containment semantics even when the stored JSON may be a scalar.
  • Makes the API less predictable.

Remove auto-wrapping and require callers to pass the exact JSON structure they want to query:

             case Query::TYPE_CONTAINS:
             case Query::TYPE_NOT_CONTAINS: {
                 $isNot = $query->getMethod() === Query::TYPE_NOT_CONTAINS;
                 $conditions = [];
                 foreach ($query->getValues() as $key => $value) {
-                    if (count($value) === 1) {
-                        $jsonKey = array_key_first($value);
-                        $jsonValue = $value[$jsonKey];
-
-                        // If scalar (e.g. "skills" => "typescript"),
-                        // wrap it to express array containment: {"skills": ["typescript"]}
-                        // If it's already an object/associative array (e.g. "config" => ["lang" => "en"]),
-                        // keep as-is to express object containment.
-                        if (!\is_array($jsonValue)) {
-                            $value[$jsonKey] = [$jsonValue];
-                        }
-                    }
                     $binds[":{$placeholder}_{$key}"] = json_encode($value);
                     $fragment = "{$alias}.{$attribute} @> :{$placeholder}_{$key}::jsonb";
                     $conditions[] = $isNot ? "NOT (" . $fragment . ")" : $fragment;
                 }
                 $separator = $isNot ? ' AND ' : ' OR ';
                 return empty($conditions) ? '' : '(' . implode($separator, $conditions) . ')';
             }
📜 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 f62ff51 and f5c0cfd.

📒 Files selected for processing (12)
  • src/Database/Adapter/Postgres.php (7 hunks)
  • src/Database/Adapter/SQL.php (1 hunks)
  • src/Database/Database.php (12 hunks)
  • src/Database/Query.php (1 hunks)
  • src/Database/Validator/Index.php (5 hunks)
  • src/Database/Validator/IndexDependency.php (1 hunks)
  • src/Database/Validator/Queries.php (1 hunks)
  • src/Database/Validator/Query/Base.php (1 hunks)
  • src/Database/Validator/Query/Filter.php (2 hunks)
  • src/Database/Validator/Structure.php (1 hunks)
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (1 hunks)
  • tests/unit/Validator/IndexTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Database/Adapter/SQL.php
  • src/Database/Query.php
  • src/Database/Validator/Query/Filter.php
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 613
File: src/Database/Adapter/Postgres.php:1254-1319
Timestamp: 2025-07-01T11:31:37.438Z
Learning: In PostgreSQL adapter methods like getUpsertStatement, complexity for database-specific SQL generation is acceptable when the main business logic is properly separated in the parent SQL adapter class, following the adapter pattern where each database adapter handles its own SQL syntax requirements.
📚 Learning: 2025-07-30T19:17:53.630Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 642
File: src/Database/Validator/PartialStructure.php:43-52
Timestamp: 2025-07-30T19:17:53.630Z
Learning: In PartialStructure validator, when filtering for required attributes validation using the $requiredAttributes parameter, $this->attributes should be used instead of the merged $attributes array because this validation is specifically for internal attributes like $createdAt and $updatedAt that are defined in the base Structure class, not collection-specific attributes.

Applied to files:

  • src/Database/Validator/Structure.php
  • tests/unit/Validator/IndexTest.php
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
📚 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:

  • src/Database/Validator/Structure.php
  • tests/unit/Validator/IndexTest.php
  • src/Database/Adapter/Postgres.php
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • src/Database/Database.php
📚 Learning: 2025-07-01T11:31:37.438Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 613
File: src/Database/Adapter/Postgres.php:1254-1319
Timestamp: 2025-07-01T11:31:37.438Z
Learning: In PostgreSQL adapter methods like getUpsertStatement, complexity for database-specific SQL generation is acceptable when the main business logic is properly separated in the parent SQL adapter class, following the adapter pattern where each database adapter handles its own SQL syntax requirements.

Applied to files:

  • src/Database/Adapter/Postgres.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:

  • src/Database/Database.php
🧬 Code graph analysis (6)
src/Database/Validator/Structure.php (1)
src/Database/Database.php (1)
  • Database (37-8427)
tests/unit/Validator/IndexTest.php (2)
src/Database/Database.php (1)
  • Database (37-8427)
src/Database/Validator/Index.php (1)
  • Index (10-579)
src/Database/Adapter/Postgres.php (7)
src/Database/Database.php (1)
  • Database (37-8427)
src/Database/Query.php (2)
  • Query (8-1177)
  • isObjectAttribute (983-986)
src/Database/Adapter/Mongo.php (1)
  • getSupportForObject (2791-2794)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (583-586)
src/Database/Adapter.php (1)
  • getSupportForObject (1078-1078)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php (6)
src/Database/Adapter.php (1)
  • Adapter (16-1408)
src/Database/Database.php (4)
  • Database (37-8427)
  • getDocument (3658-3782)
  • count (7194-7245)
  • createIndex (3435-3606)
src/Database/Validator/Index.php (1)
  • Index (10-579)
src/Database/Validator/Structure.php (1)
  • Structure (18-454)
src/Database/Query.php (6)
  • Query (8-1177)
  • equal (452-455)
  • contains (524-527)
  • notEqual (464-467)
  • select (597-600)
  • notContains (536-539)
src/Database/Adapter/Postgres.php (2)
  • getSupportForObject (2097-2100)
  • createIndex (863-920)
src/Database/Database.php (6)
src/Database/Adapter/Postgres.php (1)
  • getSupportForObject (2097-2100)
src/Database/Adapter/Mongo.php (1)
  • getSupportForObject (2791-2794)
src/Database/Adapter/Pool.php (1)
  • getSupportForObject (583-586)
src/Database/Adapter.php (1)
  • getSupportForObject (1078-1078)
src/Database/Adapter/SQLite.php (1)
  • getSupportForObject (976-979)
src/Database/Adapter/MariaDB.php (1)
  • getSupportForObject (1852-1855)
src/Database/Validator/Index.php (1)
src/Database/Database.php (1)
  • Database (37-8427)
🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php

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

(UnusedLocalVariable)


313-313: Avoid unused local variables such as '$doc7'. (undefined)

(UnusedLocalVariable)


327-327: Avoid unused local variables such as '$doc8'. (undefined)

(UnusedLocalVariable)


430-430: Avoid unused local variables such as '$doc11'. (undefined)

(UnusedLocalVariable)


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

(UnusedLocalVariable)


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

(UnusedLocalVariable)

🔇 Additional comments (17)
src/Database/Validator/IndexDependency.php (1)

83-83: LGTM!

The constant rename from self::TYPE_OBJECT to self::VAR_OBJECT correctly aligns with the new Database::VAR_OBJECT constant introduced for object-type support.

src/Database/Validator/Index.php (4)

31-31: LGTM!

The $supportForObjectIndexes parameter addition follows the established pattern for adapter capability flags, with a safe default of false.

Also applies to: 46-46


67-67: LGTM!

The constant rename to self::VAR_OBJECT correctly aligns with the new object-type constant naming convention.


137-139: LGTM!

The checkObjectIndexes validation is properly integrated into the validation chain, following the established pattern of early-return on failure.


538-578: LGTM!

The checkObjectIndexes implementation correctly validates object index constraints:

  • Ensures adapter support via $supportForObjectIndexes flag
  • Enforces single-attribute requirement
  • Prohibits explicit orders
  • Confirms attribute type is VAR_OBJECT

The validation logic follows the established pattern from spatial and vector index checks. Based on learnings.

src/Database/Database.php (9)

50-52: Add VAR_OBJECT constant — LGTM

Consistent with existing VAR_* types; no issues.


76-78: Add INDEX_OBJECT constant — LGTM

Naming aligns with INDEX_* convention; resolves prior casing feedback.


1463-1476: Wire adapter object support into IndexValidator — LGTM

Passing getSupportForObject() keeps validators capability‑aware.


2019-2029: Object attribute validation rules — LGTM

Checks for adapter support, zero size, and non‑array are correct and mirror spatial handling.


2435-2446: updateAttribute: object validation — LGTM

Mirrors create-path checks; good consistency.


3493-3498: New INDEX_OBJECT branch — LGTM

Properly guarded by adapter capability.


3532-3551: Object index constraints — LGTM

  • Single attribute enforced
  • Attribute existence/type verified
  • Orders disallowed

Matches expected JSON/JSONB GIN semantics.


3564-3577: Validator wiring for object indexes — LGTM

Keeps validation consistent across create/update code paths.


7565-7569: casting(): decode JSON string for object — LGTM

Non‑casting adapters will now return arrays for object fields.

src/Database/Adapter/Postgres.php (3)

889-910: LGTM: Correct GIN index usage for JSONB.

The INDEX_OBJECT case correctly uses USING GIN, which is PostgreSQL's standard index method for JSONB columns and supports efficient containment queries.


1858-1859: LGTM: Correct SQL type for object attributes.

Mapping VAR_OBJECT to JSONB is appropriate. JSONB is PostgreSQL's binary JSON format with indexing support.


1673-1675: LGTM: Correct integration and capability reporting.

Lines 1673–1675 properly route object queries to handleObjectQueries(). Lines 2092–2100 correctly report PostgreSQL's JSONB support.

Also applies to: 2092-2100

Comment on lines 589 to 593
// Test 3: Query with equal on indexed JSONB column
$results = $database->find($collectionId, [
Query::equal('data', [['env' => 'production']])
]);
$this->assertCount(0, $results); // Note: Object index doesn't make equal queries work differently
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix query or update comment - query doesn't match document structure.

The query searches for top-level 'env' key in data, but the documents store it as data.config.env. This query returns 0 results because the path doesn't match, not because "Object index doesn't make equal queries work differently."

Apply this diff to fix the query to match the document structure:

-        // Test 3: Query with equal on indexed JSONB column
+        // Test 3: Query with equal on nested path in indexed JSONB column
         $results = $database->find($collectionId, [
-            Query::equal('data', [['env' => 'production']])
+            Query::equal('data', [['config' => ['env' => 'production']]])
         ]);
-        $this->assertCount(0, $results); // Note: Object index doesn't make equal queries work differently
+        $this->assertCount(1, $results);
+        $this->assertEquals('gin1', $results[0]->getId());
🤖 Prompt for AI Agents
In tests/e2e/Adapter/Scopes/ObjectAttributeTests.php around lines 589 to 593,
the test queries the wrong path (it looks for top-level 'env' in data) so it
returns zero; update the Query to target the nested field (e.g.
Query::equal('data.config.env', 'production')) and adjust the expected assertion
count to match the document(s) (e.g. assertCount(1, $results)) so the query
matches the stored structure.

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

🧹 Nitpick comments (1)
src/Database/Validator/ObjectValidator.php (1)

22-25: Consider adding explicit parentheses for clarity.

While the operator precedence is correct (&& binds tighter than ||), explicit parentheses would make the intent clearer.

Apply this diff:

-    return empty($value) || is_array($value) && !array_is_list($value);
+    return empty($value) || (is_array($value) && !array_is_list($value));
📜 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 f5c0cfd and 9a96110.

📒 Files selected for processing (4)
  • src/Database/Validator/Index.php (4 hunks)
  • src/Database/Validator/ObjectValidator.php (1 hunks)
  • src/Database/Validator/Structure.php (1 hunks)
  • tests/unit/Validator/ObjectTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Database/Validator/Structure.php
🧰 Additional context used
🧬 Code graph analysis (3)
src/Database/Validator/ObjectValidator.php (4)
src/Database/Validator/Index.php (4)
  • getDescription (74-77)
  • isValid (99-141)
  • isArray (86-89)
  • getType (65-68)
src/Database/Validator/Structure.php (4)
  • getDescription (194-197)
  • isValid (208-242)
  • isArray (433-436)
  • getType (445-448)
src/Database/Validator/Query/Base.php (3)
  • getDescription (25-28)
  • isArray (37-40)
  • getType (49-52)
src/Database/Validator/Queries.php (4)
  • getDescription (44-47)
  • isValid (53-148)
  • isArray (157-160)
  • getType (169-172)
tests/unit/Validator/ObjectTest.php (1)
src/Database/Validator/ObjectValidator.php (2)
  • ObjectValidator (7-42)
  • isValid (22-25)
src/Database/Validator/Index.php (2)
src/Database/Database.php (2)
  • Database (37-8427)
  • count (7194-7245)
src/Database/Adapter.php (1)
  • count (825-825)
🔇 Additional comments (9)
src/Database/Validator/ObjectValidator.php (3)

9-15: LGTM!

The description is clear and follows the established pattern used in other validators.


27-33: LGTM!

Correctly returns false as objects are not arrays in this validation framework.


35-41: LGTM!

Correctly returns TYPE_OBJECT constant, consistent with the validator's purpose.

tests/unit/Validator/ObjectTest.php (3)

14-37: LGTM!

These test cases correctly validate associative arrays, nested structures, null values, and mixed key types. The validation logic properly handles all these scenarios.

Also applies to: 42-46


49-60: LGTM!

All invalid test cases correctly verify that list arrays, non-arrays, and numeric-only indexed arrays are rejected by the validator.


62-69: LGTM!

Empty array handling is correct (allowed by empty($value) check), and the string rejection is properly validated.

src/Database/Validator/Index.php (3)

31-31: LGTM!

The $supportForObjectIndexes parameter follows the established pattern for feature flags, with a safe default value of false.

Also applies to: 46-46


137-139: LGTM!

The object index validation is properly integrated into the validation chain, consistent with other index type checks.


538-578: LGTM!

The checkObjectIndexes() implementation is well-structured and consistent with other specialized index validators (vector, spatial). The validation logic correctly enforces:

  • Single attribute constraint
  • No explicit orders
  • Attribute must be of type VAR_OBJECT
  • Feature support check

The error messages are clear and actionable.

@ArnabChatterjee20k ArnabChatterjee20k changed the base branch from main to 3.x November 6, 2025 04:50
}
}

public function testCreateDateTimeAttributeFailure(): void
Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed this as it was creating datetime attribute without filters but now automatic filters to attributes are added

@abnegate abnegate merged commit 8062cfc into 3.x Nov 12, 2025
18 checks passed
@abnegate abnegate deleted the var_object branch November 12, 2025 08:21
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