Skip to content

Conversation

@premtsd-code
Copy link

@premtsd-code premtsd-code commented Jan 19, 2026

  • Add preserveSequence property to Database class
  • Add getPreserveSequence(), setPreserveSequence(), withPreserveSequence() methods
  • Conditionally remove $sequence in upsertDocuments based on flag
  • Add testPreserveSequenceUpsert test in GeneralTests.php

Summary by CodeRabbit

Release Notes

  • New Features
    • Added document sequence preservation capability during upsert operations with new configuration methods to control preservation behavior globally.
    • Introduced context manager functionality for temporarily enabling sequence preservation within specific operation scopes, with automatic state restoration.

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

- Add preserveSequence property to Database class
- Add getPreserveSequence(), setPreserveSequence(), withPreserveSequence() methods
- Conditionally remove $sequence in upsertDocuments based on flag
- Add testPreserveSequenceUpsert test in GeneralTests.php
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

The changes introduce a new preserveSequence feature flag to the Database class that controls whether document sequence attributes are preserved during upsert operations. New public API methods expose this flag via getters, setters, and a context manager. Conditional logic in upsertDocumentsWithIncrease removes the sequence attribute when the flag is disabled. Mirror class delegation and end-to-end tests are also added.

Changes

Cohort / File(s) Summary
Database sequence preservation feature
src/Database/Database.php
Added protected property preserveSequence (default false). Introduced public methods: getPreserveSequence(), setPreserveSequence(), and withPreserveSequence() context manager. Modified upsertDocumentsWithIncrease() to conditionally remove $sequence attribute based on flag state, mirroring existing preserveDates pattern.
Mirror delegation
src/Database/Mirror.php
Added setPreserveSequence() method that delegates through the mirror flow and assigns the property, following the existing setPreserveDates pattern.
E2E test coverage
tests/e2e/Adapter/Scopes/DocumentTests.php
Added testPreserveSequenceUpsert() test method covering adapter support detection, default behavior, flag enabling, context manager usage, and cleanup. Note: Method appears duplicated in the file.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • abnegate

Poem

🐰 Sequences now dance at the keeper's will,
Preserved or released, whatever the thrill,
With flags and with methods both simple and neat,
The database's sequence preservation's complete!
Mirror reflects what the core declares true,
Context managers wrapping it all, tried and true. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 and concisely describes the main change: introducing a preserveSequence flag for upsert operations, which is the primary feature across all modified files.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

🤖 Fix all issues with AI agents
In `@tests/e2e/Adapter/Scopes/GeneralTests.php`:
- Around line 305-314: The test currently only guards using
getSupportForUpserts(), but then calls createAttribute() which will fail on
schemaless adapters; update the guard to also check the adapter's attribute
support (e.g., call getAdapter()->getSupportForAttributes() or equivalent) and
if attributes aren't supported call $this->expectNotToPerformAssertions() and
return before invoking createAttribute(); ensure the new check is placed before
createAttribute() and references the existing getSupportForUpserts(),
createCollection(), and createAttribute() symbols so the test skips for adapters
that support upserts but not attributes.


public function testPreserveSequenceUpsert(): void
{
$this->getDatabase()->getAuthorization()->disable();
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont think authorization here is required to disable


$database->deleteCollection($collectionName);

$this->getDatabase()->getAuthorization()->reset();
Copy link
Contributor

Choose a reason for hiding this comment

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

same here like the above

return;
}

if (!$database->getAdapter()->getSupportForAttributes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

we should not skip this case here.
For context -> getSupportForAttributes() controls between schemaful and schemaless in mongodb
And mongodb supports sequence in both

Copy link
Contributor

Choose a reason for hiding this comment

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

lets move them in the e2e/documenttests.php

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 `@tests/e2e/Adapter/Scopes/DocumentTests.php`:
- Around line 1218-1237: The test uses an invalid literal sequence 999 which can
fail UUID7 adapters; change the upsert payload in the ignored-branch to supply a
valid sequence value (e.g., reuse $originalSeq1 or $originalSeq2) instead of 999
so validation passes even though setPreserveSequence(false) should ignore it;
update the Document creation passed to Database::upsertDocuments (the new
Document([... '$sequence' => ...])) to use the captured
$originalSeq1/$originalSeq2 while keeping the rest of the assertions intact.
- Around line 1225-1290: The test mutates the shared preserveSequence flag via
Database::setPreserveSequence() and leaves it false, risking cross-test state
bleed; modify the test to capture the prior state by calling $previous =
$database->getPreserveSequence() before changing it, wrap the operations that
change the flag (setPreserveSequence, withPreserveSequence, upsertDocuments,
deleteCollection) in a try/finally, and in the finally restore the original
value via $database->setPreserveSequence($previous) so the flag is always reset
even on assertion failures.

Comment on lines +1218 to +1237
$originalSeq1 = $doc1->getSequence();
$originalSeq2 = $doc2->getSequence();

$this->assertNotEmpty($originalSeq1);
$this->assertNotEmpty($originalSeq2);

// Test: Without preserveSequence (default), $sequence should be ignored
$database->setPreserveSequence(false);

$database->upsertDocuments($collectionName, [
new Document([
'$id' => 'doc1',
'$sequence' => 999, // Try to set a different sequence
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
],
'name' => 'Alice Updated',
]),
]);
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 19, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a valid (UUID7-safe) sequence value in the “ignored” branch.

'$sequence' => 999 can be invalid for UUID7 adapters; if validation happens before $sequence is stripped, this test will fail or become adapter‑order dependent. Prefer a valid sequence (e.g., the other document’s sequence) to keep the test robust and still verify “ignore” behavior.

🔧 Suggested adjustment
         $originalSeq1 = $doc1->getSequence();
         $originalSeq2 = $doc2->getSequence();

         $this->assertNotEmpty($originalSeq1);
         $this->assertNotEmpty($originalSeq2);
+        $this->assertNotEquals($originalSeq1, $originalSeq2);

         // Test: Without preserveSequence (default), $sequence should be ignored
         $database->setPreserveSequence(false);

         $database->upsertDocuments($collectionName, [
             new Document([
                 '$id' => 'doc1',
-                '$sequence' => 999, // Try to set a different sequence
+                '$sequence' => $originalSeq2, // Valid but different sequence
                 '$permissions' => [
                     Permission::read(Role::any()),
                     Permission::update(Role::any()),
                 ],
                 'name' => 'Alice Updated',
             ]),
         ]);
🤖 Prompt for AI Agents
In `@tests/e2e/Adapter/Scopes/DocumentTests.php` around lines 1218 - 1237, The
test uses an invalid literal sequence 999 which can fail UUID7 adapters; change
the upsert payload in the ignored-branch to supply a valid sequence value (e.g.,
reuse $originalSeq1 or $originalSeq2) instead of 999 so validation passes even
though setPreserveSequence(false) should ignore it; update the Document creation
passed to Database::upsertDocuments (the new Document([... '$sequence' => ...]))
to use the captured $originalSeq1/$originalSeq2 while keeping the rest of the
assertions intact.

Copy link
Contributor

Choose a reason for hiding this comment

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

the setPreserveSequence is set false so getting ignored

Copy link
Contributor

Choose a reason for hiding this comment

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

@ArnabChatterjee20k Totally—preserveSequence=false means we intend to ignore $sequence. The concern is test robustness: some adapters may validate the payload before the stripping happens, and 999 is not a valid UUID7 sequence. Using a known-valid, different sequence keeps the test adapter-agnostic while still proving the “ignore” behavior.

Proposed small tweak:

  • Ensure the two initial sequences differ.
  • Provide a valid-but-different sequence in the “ignored” branch.
         $originalSeq1 = $doc1->getSequence();
         $originalSeq2 = $doc2->getSequence();

         $this->assertNotEmpty($originalSeq1);
         $this->assertNotEmpty($originalSeq2);
+        $this->assertNotEquals($originalSeq1, $originalSeq2);

         // Test: Without preserveSequence (default), $sequence should be ignored
         $database->setPreserveSequence(false);

         $database->upsertDocuments($collectionName, [
             new Document([
                 '$id' => 'doc1',
-                '$sequence' => 999, // Try to set a different sequence
+                '$sequence' => $originalSeq2, // Valid but different sequence; should still be ignored
                 '$permissions' => [
                     Permission::read(Role::any()),
                     Permission::update(Role::any()),
                 ],
                 'name' => 'Alice Updated',
             ]),
         ]);

This keeps the intent intact (sequence must remain $originalSeq1) and avoids adapter-order/format issues.


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

Comment on lines 1225 to 1290
$database->setPreserveSequence(false);

$database->upsertDocuments($collectionName, [
new Document([
'$id' => 'doc1',
'$sequence' => 999, // Try to set a different sequence
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
],
'name' => 'Alice Updated',
]),
]);

$doc1Updated = $database->getDocument($collectionName, 'doc1');
$this->assertEquals('Alice Updated', $doc1Updated->getAttribute('name'));
$this->assertEquals($originalSeq1, $doc1Updated->getSequence()); // Sequence unchanged

// Test: With preserveSequence, $sequence should be preserved
$database->setPreserveSequence(true);

$database->upsertDocuments($collectionName, [
new Document([
'$id' => 'doc2',
'$sequence' => $originalSeq2, // Explicitly set the same sequence
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
],
'name' => 'Bob Updated',
]),
]);

$doc2Updated = $database->getDocument($collectionName, 'doc2');
$this->assertEquals('Bob Updated', $doc2Updated->getAttribute('name'));
$this->assertEquals($originalSeq2, $doc2Updated->getSequence()); // Sequence preserved

// Test: withPreserveSequence helper
$database->setPreserveSequence(false);

$doc1 = $database->getDocument($collectionName, 'doc1');
$currentSeq1 = $doc1->getSequence();

$database->withPreserveSequence(function () use ($database, $collectionName, $currentSeq1) {
$database->upsertDocuments($collectionName, [
new Document([
'$id' => 'doc1',
'$sequence' => $currentSeq1,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
],
'name' => 'Alice Final',
]),
]);
});

$doc1Final = $database->getDocument($collectionName, 'doc1');
$this->assertEquals('Alice Final', $doc1Final->getAttribute('name'));
$this->assertEquals($currentSeq1, $doc1Final->getSequence());

// Verify flag was reset after withPreserveSequence
$this->assertFalse($database->getPreserveSequence());

$database->setPreserveSequence(false);
$database->deleteCollection($collectionName);
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 19, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Restore preserveSequence to its prior state to avoid cross‑test bleed.

The test mutates a shared flag and leaves it false. If another test depends on a different prior value, this can leak state. Capture the previous value and restore it (ideally in finally) so failures don’t cascade.

🔧 Suggested adjustment
-        // Test: Without preserveSequence (default), $sequence should be ignored
-        $database->setPreserveSequence(false);
+        $previousPreserveSequence = $database->getPreserveSequence();
+        try {
+            // Test: Without preserveSequence (default), $sequence should be ignored
+            $database->setPreserveSequence(false);

         $database->upsertDocuments($collectionName, [
             new Document([
                 '$id' => 'doc1',
                 '$sequence' => $originalSeq2, // Valid but different sequence
@@
         // Verify flag was reset after withPreserveSequence
         $this->assertFalse($database->getPreserveSequence());

-        $database->setPreserveSequence(false);
-        $database->deleteCollection($collectionName);
+        } finally {
+            $database->setPreserveSequence($previousPreserveSequence);
+        }
+        $database->deleteCollection($collectionName);
🤖 Prompt for AI Agents
In `@tests/e2e/Adapter/Scopes/DocumentTests.php` around lines 1225 - 1290, The
test mutates the shared preserveSequence flag via
Database::setPreserveSequence() and leaves it false, risking cross-test state
bleed; modify the test to capture the prior state by calling $previous =
$database->getPreserveSequence() before changing it, wrap the operations that
change the flag (setPreserveSequence, withPreserveSequence, upsertDocuments,
deleteCollection) in a try/finally, and in the finally restore the original
value via $database->setPreserveSequence($previous) so the flag is always reset
even on assertion failures.

Copy link
Contributor

Choose a reason for hiding this comment

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

false is the default state

Copy link
Contributor

Choose a reason for hiding this comment

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

@ArnabChatterjee20k Totally—today’s default is false. The concern is isolation and failure-safety: this test flips the flag to true mid-way. If an assertion throws before the last reset, preserveSequence could leak as true into subsequent tests that reuse the same Database instance. Capturing the prior value and restoring it in a finally block prevents that and also future-proofs the test if the default ever changes.

Here’s a minimal, failure-safe tweak that keeps the test logic identical while guaranteeing restoration:

@@
-        // Test: Without preserveSequence (default), $sequence should be ignored
-        $database->setPreserveSequence(false);
+        $previousPreserveSequence = $database->getPreserveSequence();
+        try {
+            // Test: Without preserveSequence (default), $sequence should be ignored
+            $database->setPreserveSequence(false);
@@
-        // Verify flag was reset after withPreserveSequence
-        $this->assertFalse($database->getPreserveSequence());
-
-        $database->setPreserveSequence(false);
-        $database->deleteCollection($collectionName);
+        // Verify flag was reset after withPreserveSequence
+        $this->assertFalse($database->getPreserveSequence());
+        } finally {
+            // Always restore whatever the flag was before this test started
+            $database->setPreserveSequence($previousPreserveSequence);
+        }
+        $database->deleteCollection($collectionName);

Benefits:

  • Prevents cross-test bleed on early failures.
  • Keeps the assertion that withPreserveSequence resets to the pre-call value (false within this test).
  • Makes the test robust to future default changes and suite reordering.

If you prefer to keep it as-is, I’ll drop the suggestion. Otherwise, I can prep a follow-up patch with this change.


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

@premtsd-code premtsd-code force-pushed the feat-upsert-preserve-sequence branch from b091840 to b0586bd Compare January 19, 2026 11:41
]),
]);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

@premtsd-code can we add one more case here , where externally setting a very invalid sequence like 'abc' is throwing error

- Add setPreserveSequence delegation to Mirror.php
- Add test case for invalid $sequence value throwing StructureException
@premtsd-code premtsd-code force-pushed the feat-upsert-preserve-sequence branch from faf92c8 to cf11809 Compare January 19, 2026 12:42
if ($database->getAdapter()->getSupportForAttributes()) {
$this->fail('Expected StructureException for invalid sequence');
}
} catch (Throwable $e) {
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k Jan 19, 2026

Choose a reason for hiding this comment

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

what if not support for attributes? what is the issue coming then? check processException

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.

2 participants