Skip to content

Normalize extended ISO 8601 datetime strings in query values for Mong…#814

Open
ArnabChatterjee20k wants to merge 5 commits intomainfrom
schemaless-datetime
Open

Normalize extended ISO 8601 datetime strings in query values for Mong…#814
ArnabChatterjee20k wants to merge 5 commits intomainfrom
schemaless-datetime

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Feb 16, 2026

…oDB compatibility

Summary by CodeRabbit

  • New Features

    • MongoDB adapter now pre-processes ISO 8601 datetime strings in queries and converts them to native datetimes so equality and range filters (==, >, <, >=, <=, between) work correctly, including when schema/attribute support is disabled.
  • Tests

    • Added end-to-end tests covering datetime queries for equality, range and no-match scenarios to verify consistent filtering behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@ArnabChatterjee20k has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 19 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds ISO 8601 datetime normalization to the MongoDB adapter's buildFilter to convert matching query values into MongoDB UTCDateTime objects when attributes support is disabled or when filtering on $createdAt/$updatedAt; adds an end-to-end test exercising datetime queries across multiple comparison operators.

Changes

Cohort / File(s) Summary
MongoDB Adapter DateTime Normalization
src/Database/Adapter/Mongo.php
Inserted a preprocessing step in buildFilter that scans query values for extended ISO 8601 datetime strings and converts them to MongoDB UTCDateTime via toMongoDatetime; parsing failures leave values unchanged.
E2E Test Coverage (Schemaless)
tests/e2e/Adapter/Scopes/SchemalessTests.php
Added testQueryWithDatetime() to create a schemaless collection, insert docs with ISO 8601 datetimes, and assert results for =, >, <, >=, <= and between queries; note: the file currently contains a duplicated insertion of the same test method.

Sequence Diagram(s)

(omitted — changes are localized and do not introduce multi-component sequential flows warranting a diagram)

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feat mongodb #721: Overlaps changes to the Mongo adapter (buildFilter/toMongoDatetime) and earlier Mongo adapter implementation.

Suggested Reviewers

  • abnegate
  • fogelito

Poem

🐰 I hopped through strings of time and light,
I nudged each timestamp into UTC bright,
Queries tiptoed—equal, less, and between,
I boxed moments clean, now clearly seen,
A tiny hop, a carrot-clocked delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (11 files):

⚔️ Dockerfile (content)
⚔️ src/Database/Adapter/MariaDB.php (content)
⚔️ src/Database/Adapter/Mongo.php (content)
⚔️ src/Database/Adapter/Pool.php (content)
⚔️ src/Database/Adapter/Postgres.php (content)
⚔️ src/Database/Adapter/SQLite.php (content)
⚔️ tests/e2e/Adapter/PoolTest.php (content)
⚔️ tests/e2e/Adapter/Scopes/DocumentTests.php (content)
⚔️ tests/e2e/Adapter/Scopes/GeneralTests.php (content)
⚔️ tests/e2e/Adapter/Scopes/RelationshipTests.php (content)
⚔️ tests/e2e/Adapter/Scopes/SchemalessTests.php (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: normalizing ISO 8601 datetime strings for MongoDB compatibility, which aligns with the buildFilter normalization step added to Mongo.php.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch schemaless-datetime

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

🤖 Fix all issues with AI agents
In `@src/Database/Adapter/Mongo.php`:
- Around line 2476-2488: The current loop in the Mongo adapter unconditionally
converts ISO-like strings to UTCDateTime (using isExtendedISODatetime and
toMongoDatetime) which breaks schemaful queries; change the logic to only
normalize values when the query is schemaless or when the corresponding
attribute is known to be a datetime type—e.g. call a metadata accessor on the
Query (or buildFilter) to get the attribute type for key $k and skip conversion
unless the type === 'datetime' (or equivalent) or $query->isSchemaless() returns
true; keep the try/catch around to leave unparsable values unchanged and then
call $query->setValues($values) as before.

In `@tests/e2e/Adapter/Scopes/SchemalessTests.php`:
- Around line 3371-3372: Re-enable the collection cleanup by restoring the call
to $database->deleteCollection($col) and ensure it runs regardless of test
outcome by moving it into a finally block; locate the test that currently leaves
the collection (references $database and $col around the commented line) and
wrap the test logic in try { ... } finally { $database->deleteCollection($col);
} so the collection is always removed after the test.

Comment on lines 2476 to 2488
// Normalize extended ISO 8601 datetime strings in query values to UTCDateTime
// so they can be correctly compared against datetime fields stored in MongoDB.
$values = $query->getValues();
foreach ($values as $k => $value) {
if (is_string($value) && $this->isExtendedISODatetime($value)) {
try {
$values[$k] = $this->toMongoDatetime($value);
} catch (\Throwable $th) {
// Leave value as-is if it cannot be parsed as a datetime
}
}
}
$query->setValues($values);
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard datetime normalization by attribute type to avoid schemaful regressions.

Line 2479 converts all ISO-like strings to UTCDateTime. In schemaful mode, string attributes can store ISO-looking values; coercing the query value will stop those rows from matching (type mismatch). Please only normalize when the attribute is known to be a datetime (or when running schemaless). Otherwise this is a query correctness regression.

💡 Possible guard (stopgap)
-        $values = $query->getValues();
-        foreach ($values as $k => $value) {
-            if (is_string($value) && $this->isExtendedISODatetime($value)) {
-                try {
-                    $values[$k] = $this->toMongoDatetime($value);
-                } catch (\Throwable $th) {
-                    // Leave value as-is if it cannot be parsed as a datetime
-                }
-            }
-        }
-        $query->setValues($values);
+        // Only normalize when we can be sure the attribute represents a datetime
+        if (!$this->getSupportForAttributes() || \in_array($query->getAttribute(), ['$createdAt', '$updatedAt'], true)) {
+            $values = $query->getValues();
+            foreach ($values as $k => $value) {
+                if (is_string($value) && $this->isExtendedISODatetime($value)) {
+                    try {
+                        $values[$k] = $this->toMongoDatetime($value);
+                    } catch (\Throwable $th) {
+                        // Leave value as-is if it cannot be parsed as a datetime
+                    }
+                }
+            }
+            $query->setValues($values);
+        }

Consider wiring attribute type metadata into buildFilter/Query so custom datetime attributes are safely normalized as well.

🤖 Prompt for AI Agents
In `@src/Database/Adapter/Mongo.php` around lines 2476 - 2488, The current loop in
the Mongo adapter unconditionally converts ISO-like strings to UTCDateTime
(using isExtendedISODatetime and toMongoDatetime) which breaks schemaful
queries; change the logic to only normalize values when the query is schemaless
or when the corresponding attribute is known to be a datetime type—e.g. call a
metadata accessor on the Query (or buildFilter) to get the attribute type for
key $k and skip conversion unless the type === 'datetime' (or equivalent) or
$query->isSchemaless() returns true; keep the try/catch around to leave
unparsable values unchanged and then call $query->setValues($values) as before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in schemaless we dont have support for attribute types, but in queries it will not be an issue reason being if string the check would be o(1) as the normalization doing it and if you see the logic you will get it

Copy link
Contributor

Choose a reason for hiding this comment

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

@ArnabChatterjee20k, I understand the focus on schemaless mode, but I'd like to clarify the concern about schemaful mode:

In schemaful mode (when getSupportForAttributes() returns true), a collection could have a regular string attribute that legitimately stores ISO-formatted datetime strings as text data (e.g., event logs, API responses, human-readable timestamps).

The issue:

  • Database stores: { "logMessage": "2024-01-01T00:00:00Z" } (as a string)
  • User queries: Query::equal("logMessage", "2024-01-01T00:00:00Z")
  • Your code converts the query value to UTCDateTime object
  • MongoDB compares: string (in DB) vs UTCDateTime (in query) → no match

Pattern in your own codebase:

In castingBefore (lines 1398-1413), you already guard datetime normalization:

if (!$this->getSupportForAttributes()) {  // ← Only in schemaless mode
    foreach ($document->getArrayCopy() as $key => $value) {
        if (is_string($value) && ... && $this->isExtendedISODatetime($value)) {
            // normalize to UTCDateTime
        }
    }
}

Suggestion:
Add the same guard to buildFilter to maintain consistency and avoid breaking schemaful string queries:

// Only normalize in schemaless mode or for internal datetime attributes
if (!$this->getSupportForAttributes() || \in_array($query->getAttribute(), ['$createdAt', '$updatedAt'], true)) {
    $values = $query->getValues();
    foreach ($values as $k => $value) {
        if (is_string($value) && $this->isExtendedISODatetime($value)) {
            try {
                $values[$k] = $this->toMongoDatetime($value);
            } catch (\Throwable $th) {
                // Leave value as-is
            }
        }
    }
    $query->setValues($values);
}

Could you confirm whether schemaful collections with string attributes containing ISO-formatted text are a supported use case? If so, the guard would prevent regressions. 🤔


🧠 Learnings used
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.

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

Comment on lines 3371 to 3372
// $database->deleteCollection($col);
}
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

Re-enable collection cleanup in the new test.

Line 3371 leaves the collection behind. This can leak test data and impact later runs. Please delete the collection at the end (ideally in a finally).

🧹 Suggested fix
-        // $database->deleteCollection($col);
+        $database->deleteCollection($col);
🤖 Prompt for AI Agents
In `@tests/e2e/Adapter/Scopes/SchemalessTests.php` around lines 3371 - 3372,
Re-enable the collection cleanup by restoring the call to
$database->deleteCollection($col) and ensure it runs regardless of test outcome
by moving it into a finally block; locate the test that currently leaves the
collection (references $database and $col around the commented line) and wrap
the test logic in try { ... } finally { $database->deleteCollection($col); } so
the collection is always removed after the test.

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.

1 participant