Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Jan 19, 2026

Multi-type db was reverted
So re-reverting the commits

Fix Double-Counting of row/document Migration Status Counters

Problem

E2E test testAppwriteMigrationMixedDatabases was failing with incorrect statusCounters (e.g., row.success = 2 when only 1 row was migrated, or pending going negative). Debug logs from Cache::add() showed the same row/document IDs being processed twice within a single migration run.

Root Cause: Source + Destination Both Updated Cache for Rows/Documents

Rows and documents are special-cased in Cache to store aggregated counters per status (as strings), not per-resource objects. But they were being counted twice:

  1. Destination always calls cache->update($responseResource) during import
  2. Source also calls cache->addAll($prunedResources) after destination callback returns

For row and document, Cache::update() delegates to Cache::add(), which increments the aggregated counter. So if source also adds them, it becomes double counting.

Evidence in Code (Before Fix)

Source adds exported resources to cache after destination callback:

// vendor/utopia-php/migration/src/Migration/Source.php:60-81
$callback($returnedResources);
$this->cache->addAll($prunedResources);

Destination updates cache for every imported resource:

// vendor/utopia-php/migration/src/Migration/Destinations/Appwrite.php:240-281
foreach ($resources as $index => $resource) {
    // ...
    $responseResource = match ($resource->getGroup()) {
        Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource, $isLast),
        // ...
    };
    // ...
    $this->cache->update($responseResource);
}

Rows/documents are aggregated in cache, and update() calls add() for them:

// vendor/utopia-php/migration/src/Migration/Cache.php:88-99
public function add(Resource $resource): void
{
    if ($resource->getName() == Resource::TYPE_ROW || $resource->getName() == Resource::TYPE_DOCUMENT) {
        $status = $resource->getStatus();
        $counter = $this->cache[$resource->getName()][$status] ?? 0;
        $counter = intval($counter) + 1;
        $this->cache[$resource->getName()][$status] = $counter . '';
        return;
    }
    // ...
}
// vendor/utopia-php/migration/src/Migration/Cache.php:143-146
public function update(Resource $resource): void
{
    if ($resource->getName() == Resource::TYPE_ROW || $resource->getName() == Resource::TYPE_DOCUMENT) {
        $this->add($resource);  // ← increments counter again
        return;
    }
    // ...
}

Solution

Skip source-side cache population for row/document - destination import is already the single source of truth for these counters:

// vendor/utopia-php/migration/src/Migration/Source.php:67-74
/**
 * Rows and documents are counted in cache in an aggregated way (per status count)
 * to prevent memory blowups. Destination updates already account for these, and
 * adding them here causes double counting (source + destination).
 */
if (\in_array($resource->getName(), [Resource::TYPE_ROW, Resource::TYPE_DOCUMENT], true)) {
    continue;
}
$prunedResources[] = $resource;

Also clamp row/document pending subtraction to prevent negative values if counts drift:

// vendor/utopia-php/migration/src/Migration/Transfer.php:174-186
if ($status[$resourceType]['pending'] > 0) {
    $status[$resourceType]['pending'] -= \min($status[$resourceType]['pending'], $resource);
}

Summary by CodeRabbit

  • New Features

    • Multi-database migration: DocumentsDB and VectorDB supported.
    • Many new attribute/column types: vector, object, enum, relationship, boolean, datetime, decimal, integer, email, ip, url, point/line/polygon, etc.
  • Changes

    • Export/import and reader flows operate per-database with richer entity/record/field payloads.
    • CSV export improved to handle complex value shapes and document/collection exports.
    • Cache keys now include database type and source counting adjusted to avoid double-counting.

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

This reverts commit 7fcb7a1.
This reverts commit 2dc0108.
This reverts commit be4d49d.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Walkthrough

Adds multi-database support across the migration subsystem: new database resource types (DocumentsDB, VectorDB, legacy), Collection and Document models, and DATABASE/ENTITY/FIELD/RECORD mapping constants. Introduces an abstract Attribute class and many concrete attribute/column types (text, integer, decimal, enum, vector, object, geo types, relationship, boolean, datetime, email, ip, url). Refactors Appwrite source/reader and Appwrite destination to resolve per-database contexts via callables/readers and per-database DSNs, updates CSV handling to emit document/collection/attribute payloads, and appends database type into resource cache keys.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Multitype db' is vague and lacks specificity about the actual implementation scope, being only 2 words without clear descriptive detail. Consider expanding the title to clarify the scope, such as 'Add support for multiple database types (TablesDB, DocumentsDB, VectorDB)' or similar to better convey the primary change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

Caution

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

⚠️ Outside diff range comments (1)
src/Migration/Destinations/Appwrite.php (1)

352-363: Normalize database type before DSN resolution.

The type field normalizes empty getType() to 'legacy', but the DSN lookup receives getType() directly, which could be an empty string. If the DSN resolver doesn't handle empty strings, database metadata can point to the wrong DSN.

Suggested fix
+        $databaseType = $resource->getType() ?: 'legacy';
         $database = $this->dbForProject->createDocument('databases', new UtopiaDocument([
             '$id' => $resource->getId(),
             'name' => $resource->getDatabaseName(),
             'enabled' => $resource->getEnabled(),
             'search' => implode(' ', [$resource->getId(), $resource->getDatabaseName()]),
             '$createdAt' => $resource->getCreatedAt(),
             '$updatedAt' => $resource->getUpdatedAt(),
             'originalId' => empty($resource->getOriginalId()) ? null : $resource->getOriginalId(),
-            'type' => empty($resource->getType()) ? 'legacy' : $resource->getType(),
+            'type' => $databaseType,
             // source and destination can be in different location
-            'database' => ($this->getDatabaseDSN)($resource->getType())
+            'database' => ($this->getDatabaseDSN)($databaseType)
         ]));
🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 459-464: The guard in createField(Column|Attribute $resource)
currently checks Resource::TYPE_DATABASE_DOCUMENTSDB and sets status with
message "Columns not supported for DocumentsDB" but the method receives both
Column and Attribute; either narrow the condition to only apply when $resource
is an instance of Column, or change the status message to accurately reflect
both types (e.g., "Columns and Attributes not supported for DocumentsDB" or
"Fields not supported for DocumentsDB"); update the check around createField and
the call to $resource->setStatus(Resource::STATUS_SKIPPED, ...) accordingly so
the behavior and message match (refer to createField, Column, Attribute,
Resource::TYPE_DATABASE_DOCUMENTSDB, setStatus, STATUS_SKIPPED).

In `@src/Migration/Resources/Database/Attribute/Decimal.php`:
- Around line 22-23: The current defaulting in Decimal.php uses PHP_FLOAT_MIN
for $min which is the smallest positive float, not a negative bound; update the
default to use -PHP_FLOAT_MAX (or set $min ??= -PHP_FLOAT_MAX) so decimal ranges
allow negative values, keeping $max ??= PHP_FLOAT_MAX unchanged and adjust any
references to $min/$max in the Decimal class or related validation to expect a
potentially negative lower bound.

In `@src/Migration/Resources/Database/Attribute/Integer.php`:
- Around line 78-91: The fromArray method for class Integer is not
reading/passing the constructor's $signed parameter, causing unsigned integers
to be lost; update Integer::fromArray to extract the signed flag (e.g.
$array['signed'] ?? false) and pass it into the Integer constructor (the
__construct parameter named $signed), and also update the method PHPDoc to
document the signed field being deserialized.

In `@src/Migration/Resources/Database/Attribute/Polygon.php`:
- Around line 58-67: In Polygon::fromArray change the handling of the 'default'
index to guard against missing keys: update the constructor call in the
fromArray method to pass $array['default'] ?? null (matching other attribute
factories' null fallback) so an undefined index notice is avoided; leave other
fields (required, createdAt, updatedAt) as-is and locate this in the fromArray
method of the Polygon class to apply the fix.

In `@src/Migration/Resources/Database/VectorDB.php`:
- Around line 14-24: The PHPDoc for the array parameter is missing the "type"
key even though the code reads $array['type'] (see use in the VectorDB
class/method handling that array); update the `@param` array shape to include
type: string so the documented shape matches runtime access (add "type: string"
to the array shape in the PHPDoc for the $array parameter).

In `@src/Migration/Sources/Appwrite.php`:
- Around line 851-857: The array construction is using $databaseName (which
holds the database type) for the 'name' field; update the assignment to use the
actual database name accessor on the $database object (e.g., replace
$databaseName with $database->getDatabaseName() or $database->getName()
depending on the available method) inside the block that builds the 'database'
array so the 'name' value reflects the real database name rather than its type.

In `@src/Migration/Sources/Appwrite/Reader/Database.php`:
- Around line 27-38: The Database class is missing implementations for
getDatabase() and logDebug() which are invoked from within the class; add a
private getDatabase(): UtopiaDatabase method that returns the correct DB
instance by invoking the stored callable $this->getDatabasesDB with
$this->projectId when callable (and falling back to $this->dbForProject), and
add a private logDebug(string $message, array $context = []): void method that
emits debug output (format the message with context) using an existing logger if
present (e.g., $this->logger) or error_log as a fallback so existing calls to
getDatabase() and logDebug() succeed.

In `@src/Migration/Transfer.php`:
- Line 361: The variable $groupDatabasesIndex is assigned from
array_search(Transfer::GROUP_DATABASES, $services) but never used; remove the
unused assignment line to eliminate the dead variable (i.e. delete the statement
that sets $groupDatabasesIndex) so only relevant variables and logic remain; if
the intent was to check presence of Transfer::GROUP_DATABASES in $services
instead, replace the assignment with an explicit conditional using
in_array(Transfer::GROUP_DATABASES, $services) where needed.
🧹 Nitpick comments (13)
src/Migration/Resources/Database/Database.php (1)

62-72: Consider including database in jsonSerialize() for round-trip consistency.

The new $database property is not included in jsonSerialize(). If resources are serialized and later reconstructed via fromArray(), the database value will be lost. If this is intentional (e.g., database is only for internal routing), consider documenting it; otherwise, add it to the serialized output.

Suggested fix (if round-trip is needed)
     public function jsonSerialize(): array
     {
         return [
             'id' => $this->id,
             'name' => $this->name,
             'createdAt' => $this->createdAt,
             'updatedAt' => $this->updatedAt,
             'enabled' => $this->enabled,
             'type' => $this->type,
+            'database' => $this->database,
         ];
     }
src/Migration/Resources/Database/Attribute/Vector.php (1)

61-72: Minor formatting nit: missing space after colon.

Line 67 has size:$array['size'] without a space after the colon, unlike other named arguments. This is inconsistent with the rest of the codebase formatting.

🔧 Suggested fix
     public static function fromArray(array $array): self
     {
         return new self(
             $array['key'],
             Collection::fromArray($array['table'] ?? $array['collection']),
             required: $array['required'],
-            size:$array['size'],
+            size: $array['size'],
             default: $array['default'],
             createdAt: $array['createdAt'] ?? '',
             updatedAt: $array['updatedAt'] ?? '',
         );
     }
src/Migration/Resources/Database/Columns/Vector.php (1)

61-72: Minor formatting nit: missing space after colon.

Line 67 has size:$array['size'] without a space after the colon, inconsistent with other named arguments in the file and codebase.

🔧 Suggested fix
     public static function fromArray(array $array): self
     {
         return new self(
             $array['key'],
             Table::fromArray($array['table'] ?? $array['collection']),
             required: $array['required'],
-            size:$array['size'],
+            size: $array['size'],
             default: $array['default'],
             createdAt: $array['createdAt'] ?? '',
             updatedAt: $array['updatedAt'] ?? '',
         );
     }
src/Migration/Resources/Database/Attribute/Text.php (1)

88-96: Redundant method overrides.

Per the relevant code snippet from src/Migration/Resources/Database/Attribute.php, the base Attribute class already defines getSize() and getFormat() with identical implementations. These overrides can be removed.

♻️ Suggested removal
     public function getType(): string
     {
         return Attribute::TYPE_STRING;
     }
-
-    public function getSize(): int
-    {
-        return $this->size;
-    }
-
-    public function getFormat(): string
-    {
-        return $this->format;
-    }
 }
src/Migration/Resources/Database/DocumentsDB.php (1)

14-24: PHPDoc is missing the type key.

The implementation uses $array['type'] ?? 'legacy' on line 34, but the PHPDoc shape definition doesn't include the type key. Consider adding it for accuracy.

📝 Suggested fix
     /**
      * `@param` array{
      *     id: string,
      *     name: string,
      *     createdAt: string,
      *     updatedAt: string,
      *     enabled: bool,
      *     originalId: string|null,
+     *     type: string,
      *     database: string
      * } $array
      */
src/Migration/Resources/Database/Attribute/Enum.php (1)

98-101: Unnecessary array cast.

The formatOptions['elements'] is always an array by construction (set in constructor line 33 and guaranteed by PHPDoc). The (array) cast is redundant.

♻️ Suggested simplification
     public function getElements(): array
     {
-        return (array)$this->formatOptions['elements'];
+        return $this->formatOptions['elements'];
     }
src/Migration/Resources/Database/Attribute/Integer.php (1)

98-106: Return type ?int is misleading.

Both getMin() and getMax() are declared as returning ?int, but they always return an int due to the cast and because the constructor defaults min/max to PHP_INT_MIN/PHP_INT_MAX. Consider changing the return type to int for accuracy, or removing the cast if null should actually be possible.

♻️ Suggested fix (if null is never expected)
-    public function getMin(): ?int
+    public function getMin(): int
     {
         return (int)$this->formatOptions['min'];
     }

-    public function getMax(): ?int
+    public function getMax(): int
     {
         return (int)$this->formatOptions['max'];
     }
src/Migration/Resources/Database/Attribute/Decimal.php (1)

96-104: Return type inconsistency in getMin/getMax.

The return type is ?float (nullable), but the implementation always casts to (float), which will never return null. Either remove the nullable return type or handle the null case.

Proposed fix
     public function getMin(): ?float
     {
-        return (float)$this->formatOptions['min'];
+        return isset($this->formatOptions['min']) ? (float)$this->formatOptions['min'] : null;
     }

     public function getMax(): ?float
     {
-        return (float)$this->formatOptions['max'];
+        return isset($this->formatOptions['max']) ? (float)$this->formatOptions['max'] : null;
     }
src/Migration/Resources/Database/Attribute/Relationship.php (1)

96-99: Minor: getRelatedTable() fallback is effectively dead code.

Since the constructor always stores the value under 'relatedCollection', the 'relatedTable' key will never exist in $this->options. The null coalescing fallback works correctly, but the first check is unnecessary.

Optional simplification
     public function getRelatedTable(): string
     {
-        return $this->options['relatedTable'] ?? $this->options['relatedCollection'];
+        return $this->options['relatedCollection'];
     }

However, keeping the fallback may be intentional for compatibility if fromArray is used with data containing 'relatedTable'.

src/Migration/Sources/Appwrite.php (1)

92-95: PHPDoc typo: UtopiaDocument should be a valid type.

The type hint UtopiaDocument appears to be incomplete. It should likely be \Utopia\Database\Document or similar.

Proposed fix
     /**
-     * `@var` callable(UtopiaDocument $database|null): UtopiaDatabase
+     * `@var` callable(\Utopia\Database\Document|null $database): UtopiaDatabase
      */
     protected mixed $getDatabasesDB;
src/Migration/Destinations/Appwrite.php (3)

797-808: Drop unused $supportForSpatialAttributes (or pass into validator).
PHPMD flagged it as unused; keeping it adds noise.

♻️ Suggested cleanup
-        $supportForSpatialAttributes = $dbForDatabases->getAdapter()->getSupportForSpatialAttributes();

962-975: Avoid unused $value in the row scrub loop.
Small cleanup to satisfy PHPMD.

♻️ Suggested cleanup
-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {

1522-1525: Remove unused $columnSize.
PHPMD flags this; drop it unless you plan to use size for index lengths.

♻️ Suggested cleanup
-            $columnSize = $oldColumns[$columnIndex]['size'];

Comment on lines +22 to +23
$min ??= PHP_FLOAT_MIN;
$max ??= PHP_FLOAT_MAX;
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 | 🟠 Major

Potential issue with PHP_FLOAT_MIN as minimum bound.

PHP_FLOAT_MIN represents the smallest positive float (~2.2e-308), not a negative minimum. This differs from Integer.php which correctly uses PHP_INT_MIN (a negative value). For decimal/float ranges, if you intend to allow negative values, consider using -PHP_FLOAT_MAX as the lower bound.

Proposed fix
-        $min ??= PHP_FLOAT_MIN;
+        $min ??= -PHP_FLOAT_MAX;
🤖 Prompt for AI Agents
In `@src/Migration/Resources/Database/Attribute/Decimal.php` around lines 22 - 23,
The current defaulting in Decimal.php uses PHP_FLOAT_MIN for $min which is the
smallest positive float, not a negative bound; update the default to use
-PHP_FLOAT_MAX (or set $min ??= -PHP_FLOAT_MAX) so decimal ranges allow negative
values, keeping $max ??= PHP_FLOAT_MAX unchanged and adjust any references to
$min/$max in the Decimal class or related validation to expect a potentially
negative lower bound.

Comment on lines +78 to +91
public static function fromArray(array $array): self
{
return new self(
$array['key'],
Collection::fromArray($array['table'] ?? $array['collection']),
required: $array['required'],
default: $array['default'],
array: $array['array'],
min: $array['formatOptions']['min'] ?? null,
max: $array['formatOptions']['max'] ?? null,
createdAt: $array['createdAt'] ?? '',
updatedAt: $array['updatedAt'] ?? '',
);
}
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

Missing signed parameter in fromArray.

The constructor accepts a $signed parameter (line 18), but fromArray doesn't extract or pass it. This causes data loss during deserialization—unsigned integers will be incorrectly reconstructed as signed.

🐛 Suggested fix
     public static function fromArray(array $array): self
     {
         return new self(
             $array['key'],
             Collection::fromArray($array['table'] ?? $array['collection']),
             required: $array['required'],
             default: $array['default'],
             array: $array['array'],
             min: $array['formatOptions']['min'] ?? null,
             max: $array['formatOptions']['max'] ?? null,
+            signed: $array['signed'] ?? true,
             createdAt: $array['createdAt'] ?? '',
             updatedAt: $array['updatedAt'] ?? '',
         );
     }

Also update the PHPDoc to include:

      *     formatOptions: array{
      *         min: ?int,
      *         max: ?int
      *     },
+     *     signed: bool,
      *     createdAt: string,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static function fromArray(array $array): self
{
return new self(
$array['key'],
Collection::fromArray($array['table'] ?? $array['collection']),
required: $array['required'],
default: $array['default'],
array: $array['array'],
min: $array['formatOptions']['min'] ?? null,
max: $array['formatOptions']['max'] ?? null,
createdAt: $array['createdAt'] ?? '',
updatedAt: $array['updatedAt'] ?? '',
);
}
public static function fromArray(array $array): self
{
return new self(
$array['key'],
Collection::fromArray($array['table'] ?? $array['collection']),
required: $array['required'],
default: $array['default'],
array: $array['array'],
min: $array['formatOptions']['min'] ?? null,
max: $array['formatOptions']['max'] ?? null,
signed: $array['signed'] ?? true,
createdAt: $array['createdAt'] ?? '',
updatedAt: $array['updatedAt'] ?? '',
);
}
🤖 Prompt for AI Agents
In `@src/Migration/Resources/Database/Attribute/Integer.php` around lines 78 - 91,
The fromArray method for class Integer is not reading/passing the constructor's
$signed parameter, causing unsigned integers to be lost; update
Integer::fromArray to extract the signed flag (e.g. $array['signed'] ?? false)
and pass it into the Integer constructor (the __construct parameter named
$signed), and also update the method PHPDoc to document the signed field being
deserialized.

Comment on lines +58 to +67
public static function fromArray(array $array): self
{
return new self(
$array['key'],
Collection::fromArray($array['table'] ?? $array['collection']),
required: $array['required'],
default: $array['default'],
createdAt: $array['createdAt'] ?? '',
updatedAt: $array['updatedAt'] ?? '',
);
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

Guard against missing default key.
If default is absent, this raises an undefined index notice. Use a null fallback for consistency with other attribute factories.

🛠️ Proposed fix
-            default: $array['default'],
+            default: $array['default'] ?? null,
🤖 Prompt for AI Agents
In `@src/Migration/Resources/Database/Attribute/Polygon.php` around lines 58 - 67,
In Polygon::fromArray change the handling of the 'default' index to guard
against missing keys: update the constructor call in the fromArray method to
pass $array['default'] ?? null (matching other attribute factories' null
fallback) so an undefined index notice is avoided; leave other fields (required,
createdAt, updatedAt) as-is and locate this in the fromArray method of the
Polygon class to apply the fix.

Comment on lines +14 to +24
/**
* @param array{
* id: string,
* name: string,
* createdAt: string,
* updatedAt: string,
* enabled: bool,
* originalId: string|null,
* database: string
* } $array
*/
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

PHPDoc is missing the type key.

The @param array shape documents database: string but omits the type key, even though line 34 accesses $array['type']. Add type: string to the PHPDoc for consistency.

Suggested fix
     /**
      * `@param` array{
      *     id: string,
      *     name: string,
      *     createdAt: string,
      *     updatedAt: string,
      *     enabled: bool,
      *     originalId: string|null,
+     *     type: string,
      *     database: string
      * } $array
      */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @param array{
* id: string,
* name: string,
* createdAt: string,
* updatedAt: string,
* enabled: bool,
* originalId: string|null,
* database: string
* } $array
*/
/**
* `@param` array{
* id: string,
* name: string,
* createdAt: string,
* updatedAt: string,
* enabled: bool,
* originalId: string|null,
* type: string,
* database: string
* } $array
*/
🤖 Prompt for AI Agents
In `@src/Migration/Resources/Database/VectorDB.php` around lines 14 - 24, The
PHPDoc for the array parameter is missing the "type" key even though the code
reads $array['type'] (see use in the VectorDB class/method handling that array);
update the `@param` array shape to include type: string so the documented shape
matches runtime access (add "type: string" to the array shape in the PHPDoc for
the $array parameter).

Comment on lines +851 to +857
'database' => [
'id' => $database->getId(),
'name' => $databaseName,
'type' => $database->getType(),
'database' => $database->getDatabase(),
]
]);
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 | 🟠 Major

Potential bug: 'name' => $databaseName passes the database type instead of actual name.

At line 853, $databaseName is the database type string (e.g., 'database', 'documentsdb'), not the actual database name. This should likely be $database->getDatabaseName() or similar.

Proposed fix
                     $newTable = self::getEntity($databaseName, [
                         'id' => $table['$id'],
                         'name' => $table['name'],
                         'documentSecurity' => $table['documentSecurity'],
                         'permissions' => $table['$permissions'],
                         'createdAt' => $table['$createdAt'],
                         'updatedAt' => $table['$updatedAt'],
                         'database' => [
                             'id' => $database->getId(),
-                            'name' => $databaseName,
+                            'name' => $database->getDatabaseName(),
                             'type' => $database->getType(),
                             'database' => $database->getDatabase(),
                         ]
                     ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'database' => [
'id' => $database->getId(),
'name' => $databaseName,
'type' => $database->getType(),
'database' => $database->getDatabase(),
]
]);
'database' => [
'id' => $database->getId(),
'name' => $database->getDatabaseName(),
'type' => $database->getType(),
'database' => $database->getDatabase(),
]
]);
🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite.php` around lines 851 - 857, The array
construction is using $databaseName (which holds the database type) for the
'name' field; update the assignment to use the actual database name accessor on
the $database object (e.g., replace $databaseName with
$database->getDatabaseName() or $database->getName() depending on the available
method) inside the block that builds the 'database' array so the 'name' value
reflects the real database name rather than its type.

Comment on lines +27 to 38
/**
* @var callable(UtopiaDocument|null): UtopiaDatabase
*/
private mixed $getDatabasesDB;

public function __construct(
private readonly UtopiaDatabase $dbForProject,
?callable $getDatabasesDB = null,
private readonly ?string $projectId = null
) {
$this->getDatabasesDB = $getDatabasesDB;
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getDatabase and logDebug methods are defined in Reader interface or related files
echo "=== Checking Reader interface ==="
cat src/Migration/Sources/Appwrite/Reader.php 2>/dev/null || echo "Reader.php not found"

echo ""
echo "=== Searching for getDatabase method definition ==="
rg -n 'function getDatabase\(' --type=php -C2

echo ""
echo "=== Searching for logDebug method definition ==="
rg -n 'function logDebug\(' --type=php -C2

Repository: utopia-php/migration

Length of output: 4579


🏁 Script executed:

#!/bin/bash
# Check the Database.php class definition (parent class, traits, etc.)
echo "=== Checking Database.php class definition ==="
head -60 src/Migration/Sources/Appwrite/Reader/Database.php

echo ""
echo "=== Searching for logDebug in the entire codebase with context ==="
rg -i 'logdebug' --type=php -C2

echo ""
echo "=== Checking if Database class extends a parent or uses traits ==="
rg -n 'class Database' src/Migration/Sources/Appwrite/Reader/Database.php -A 5

echo ""
echo "=== Checking if getDatabase is called in Database.php and what parameters it takes ==="
rg -n 'getDatabase' src/Migration/Sources/Appwrite/Reader/Database.php -B2 -A2

Repository: utopia-php/migration

Length of output: 9920


Fix missing method implementations: getDatabase() and logDebug()

The Database class calls $this->getDatabase() at lines 343, 398, and 496, and $this->logDebug() at lines 346 and 348, but these methods do not exist. The class implements only the Reader interface, has no parent class, and no traits that would provide these methods. The logDebug() method does not exist anywhere in the codebase, and getDatabase() is not defined in this class, its interface, or any parent. Either implement these methods in the Database class or provide them through a parent class or trait.

🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite/Reader/Database.php` around lines 27 - 38, The
Database class is missing implementations for getDatabase() and logDebug() which
are invoked from within the class; add a private getDatabase(): UtopiaDatabase
method that returns the correct DB instance by invoking the stored callable
$this->getDatabasesDB with $this->projectId when callable (and falling back to
$this->dbForProject), and add a private logDebug(string $message, array $context
= []): void method that emits debug output (format the message with context)
using an existing logger if present (e.g., $this->logger) or error_log as a
fallback so existing calls to getDatabase() and logDebug() succeed.

public static function extractServices(array $services): array
{
$resources = [];
$groupDatabasesIndex = array_search(Transfer::GROUP_DATABASES, $services);
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

Remove unused variable $groupDatabasesIndex.

This variable is assigned but never used, as flagged by static analysis.

Proposed fix
-        $groupDatabasesIndex = array_search(Transfer::GROUP_DATABASES, $services);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$groupDatabasesIndex = array_search(Transfer::GROUP_DATABASES, $services);
🧰 Tools
🪛 PHPMD (2.15.0)

361-361: Avoid unused local variables such as '$groupDatabasesIndex'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@src/Migration/Transfer.php` at line 361, The variable $groupDatabasesIndex is
assigned from array_search(Transfer::GROUP_DATABASES, $services) but never used;
remove the unused assignment line to eliminate the dead variable (i.e. delete
the statement that sets $groupDatabasesIndex) so only relevant variables and
logic remain; if the intent was to check presence of Transfer::GROUP_DATABASES
in $services instead, replace the assignment with an explicit conditional using
in_array(Transfer::GROUP_DATABASES, $services) where 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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/Migration/Sources/Appwrite.php (1)

733-745: Root database filter skips VectorDB.

Line 733 only checks TYPE_DATABASE and TYPE_DATABASE_DOCUMENTSDB, so a VectorDB root export won’t be filtered by ID and can export all databases. Add TYPE_DATABASE_VECTORDB to the condition.

🔧 Suggested fix
-            if ($this->rootResourceId !== '' && ($this->rootResourceType === Resource::TYPE_DATABASE || $this->rootResourceType === Resource::TYPE_DATABASE_DOCUMENTSDB)) {
+            if (
+                $this->rootResourceId !== '' &&
+                \in_array($this->rootResourceType, [
+                    Resource::TYPE_DATABASE,
+                    Resource::TYPE_DATABASE_DOCUMENTSDB,
+                    Resource::TYPE_DATABASE_VECTORDB,
+                ], true)
+            ) {
🤖 Fix all issues with AI agents
In `@src/Migration/Sources/Appwrite.php`:
- Around line 1072-1084: The code calls getRecord with the database name instead
of its type which makes record creation default to Row; change the first
argument of getRecord to use $table->getDatabase()->getType() (instead of
getDatabaseName()) so getRecord receives the expected database type; update the
call site in the Appwrite migration code where getRecord(...) is invoked and
ensure related metadata (the rest of the associative array built from $table and
$table->getDatabase()) remains unchanged.
- Around line 991-1034: Remove the unused local variables that PHPMD flags:
delete the $memUsage assignment and the two $timestamp assignments around the
listRows call in the method that iterates table rows (the block using
$this->reader->listRows($table, $queries)); alternatively, if you want to keep
those diagnostics, re-enable the commented $this->logDebugTrackedProject(...)
calls or guard them behind a debug flag so the variables are actually used
(refer to the logDebugTrackedProject call sites and the $this->reader->listRows
invocation when making the change).

In `@src/Migration/Sources/Appwrite/Reader/Database.php`:
- Around line 91-97: The code reads $database->getAttribute('type') and
immediately indexes Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType], which
can throw an undefined index for missing/unexpected types; update the logic in
the block around $databaseType and $databaseSpecificResources to default to
Resource::TYPE_DATABASE when getAttribute('type') returns null/empty or when
$databaseType is not a key in Resource::DATABASE_TYPE_RESOURCE_MAP (also keep
the existing normalization for Resource::TYPE_DATABASE_LEGACY and
Resource::TYPE_DATABASE_TABLESDB), e.g. compute $databaseType with a fallback
and guard the lookup with isset/array_key_exists before assigning
$databaseSpecificResources so it never indexes an unknown key.
♻️ Duplicate comments (1)
src/Migration/Sources/Appwrite.php (1)

830-842: Database name is set to type string, not actual name.

Line 839 still assigns 'name' => $databaseName, but $databaseName holds the database type (e.g., database, documentsdb) rather than the actual database name. This was already raised previously.

🔧 Suggested fix
-                            'name' => $databaseName,
+                            'name' => $database->getDatabaseName(),

Comment on lines +991 to +1034
$iterationCount++;

$memUsage = round(memory_get_usage(true) / 1024 / 1024, 2);
// $this->logDebugTrackedProject("Table: {$table->getName()} | Iteration: $iterationCount | Memory: {$memUsage}MB | LastRow: " . ($lastRow ? $lastRow->getId() : 'null'));

$queries = [
$this->database->queryLimit($batchSize),
$this->reader->queryLimit($batchSize),
...$this->queries,
];

$rows = [];

if ($lastRow) {
$queries[] = $this->database->queryCursorAfter($lastRow);
$queries[] = $this->reader->queryCursorAfter($lastRow);
}

$selects = ['*', '$id', '$permissions', '$updatedAt', '$createdAt']; // We want relations flat!
$manyToMany = [];

$attributes = $this->cache->get(Column::getName());
foreach ($attributes as $attribute) {
/** @var Relationship $attribute */
if (
$attribute->getTable()->getId() === $table->getId() &&
$attribute->getType() === Column::TYPE_RELATIONSHIP &&
$attribute->getSide() === 'parent' &&
$attribute->getRelationType() == 'manyToMany'
) {
/**
* Blockers:
* we should use but Does not work properly:
* $selects[] = $attribute->getKey() . '.$id';
* when selecting for a relation we get all relations not just the one we were asking.
* when selecting for a relation like select(*, relation.$id) , all relations get resolve
*/
$manyToMany[] = $attribute->getKey();
if ($this->reader->getSupportForAttributes()) {
$attributes = $this->cache->get($fieldName);

foreach ($attributes as $attribute) {
/** @var Relationship $attribute */
if (
$attribute->getTable()->getId() === $table->getId() &&
$attribute->getType() === Column::TYPE_RELATIONSHIP &&
$attribute->getSide() === 'parent' &&
$attribute->getRelationType() == 'manyToMany'
) {
$manyToMany[] = $attribute->getKey();
}
}
}
/** @var Column|Relationship $attribute */

$queries[] = $this->database->querySelect($selects);
$queries[] = $this->reader->querySelect($selects);

$timestamp = microtime(true);
// $this->logDebugTrackedProject("BEFORE listRows() | Table: {$table->getName()} | Batch: $batchSize | Timestamp: {$timestamp}");

$response = $this->database->listRows($table, $queries);
$response = $this->reader->listRows($table, $queries);

$timestamp = microtime(true);
// $this->logDebugTrackedProject("AFTER listRows() | Table: {$table->getName()} | Rows: " . count($response) . " | Timestamp: $timestamp");
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

Remove unused local variables to satisfy PHPMD.

$memUsage (Line 993) and $timestamp (Lines 1028/1033) are computed but never used because the debug logs are commented out. PHPMD flags these as unused locals. Either re-enable logging behind a flag or remove the variables.

🔧 Suggested cleanup
-                $memUsage = round(memory_get_usage(true) / 1024 / 1024, 2);
-                // $this->logDebugTrackedProject("Table: {$table->getName()} | Iteration: $iterationCount | Memory: {$memUsage}MB | LastRow: " . ($lastRow ? $lastRow->getId() : 'null'));
+                // $this->logDebugTrackedProject("Table: {$table->getName()} | Iteration: $iterationCount | Memory: " . round(memory_get_usage(true) / 1024 / 1024, 2) . "MB | LastRow: " . ($lastRow ? $lastRow->getId() : 'null'));
 
-                $timestamp = microtime(true);
                 // $this->logDebugTrackedProject("BEFORE listRows() | Table: {$table->getName()} | Batch: $batchSize | Timestamp: {$timestamp}");
 
-                $response = $this->reader->listRows($table, $queries);
-
-                $timestamp = microtime(true);
-                // $this->logDebugTrackedProject("AFTER listRows() | Table: {$table->getName()} | Rows: " . count($response) . " | Timestamp: $timestamp");
+                $response = $this->reader->listRows($table, $queries);
+                // $this->logDebugTrackedProject("AFTER listRows() | Table: {$table->getName()} | Rows: " . count($response) . " | Timestamp: " . microtime(true));
🧰 Tools
🪛 PHPMD (2.15.0)

993-993: Avoid unused local variables such as '$memUsage'. (undefined)

(UnusedLocalVariable)


1028-1028: Avoid unused local variables such as '$timestamp'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite.php` around lines 991 - 1034, Remove the
unused local variables that PHPMD flags: delete the $memUsage assignment and the
two $timestamp assignments around the listRows call in the method that iterates
table rows (the block using $this->reader->listRows($table, $queries));
alternatively, if you want to keep those diagnostics, re-enable the commented
$this->logDebugTrackedProject(...) calls or guard them behind a debug flag so
the variables are actually used (refer to the logDebugTrackedProject call sites
and the $this->reader->listRows invocation when making the change).

Comment on lines +1072 to +1084
$row = self::getRecord($table->getDatabase()->getDatabaseName(), [
'id' => $id,
'table' => [
'id' => $table->getId(),
'name' => $table->getTableName(),
'rowSecurity' => $table->getRowSecurity(),
'permissions' => $table->getPermissions(),
'database' => [
'id' => $table->getDatabase()->getId(),
'name' => $table->getDatabase()->getDatabaseName(),
'type' => $table->getDatabase()->getType(),
'database' => $table->getDatabase()->getDatabase(),
]
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 | 🟠 Major

Record type selection uses database name instead of database type.

Line 1072 calls getRecord($table->getDatabase()->getDatabaseName(), ...). getRecord() expects a database type (TYPE_DATABASE_DOCUMENTSDB, TYPE_DATABASE_VECTORDB, etc.), so passing the database name will cause Document DB/Vector DB records to be created as Row by default.

🔧 Suggested fix
-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$row = self::getRecord($table->getDatabase()->getDatabaseName(), [
'id' => $id,
'table' => [
'id' => $table->getId(),
'name' => $table->getTableName(),
'rowSecurity' => $table->getRowSecurity(),
'permissions' => $table->getPermissions(),
'database' => [
'id' => $table->getDatabase()->getId(),
'name' => $table->getDatabase()->getDatabaseName(),
'type' => $table->getDatabase()->getType(),
'database' => $table->getDatabase()->getDatabase(),
]
$row = self::getRecord($table->getDatabase()->getType(), [
'id' => $id,
'table' => [
'id' => $table->getId(),
'name' => $table->getTableName(),
'rowSecurity' => $table->getRowSecurity(),
'permissions' => $table->getPermissions(),
'database' => [
'id' => $table->getDatabase()->getId(),
'name' => $table->getDatabase()->getDatabaseName(),
'type' => $table->getDatabase()->getType(),
'database' => $table->getDatabase()->getDatabase(),
]
🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite.php` around lines 1072 - 1084, The code calls
getRecord with the database name instead of its type which makes record creation
default to Row; change the first argument of getRecord to use
$table->getDatabase()->getType() (instead of getDatabaseName()) so getRecord
receives the expected database type; update the call site in the Appwrite
migration code where getRecord(...) is invoked and ensure related metadata (the
rest of the associative array built from $table and $table->getDatabase())
remains unchanged.

Comment on lines 91 to 97
$databaseType = $database->getAttribute('type');
if (in_array($databaseType, [Resource::TYPE_DATABASE_LEGACY,Resource::TYPE_DATABASE_TABLESDB])) {
$databaseType = Resource::TYPE_DATABASE;
}

if (!Resource::isSupported([Resource::TYPE_ROW, Resource::TYPE_COLUMN, Resource::TYPE_INDEX], $resources)) {
continue;
}
$databaseSpecificResources = Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType];

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 | 🟠 Major

Guard against missing/unknown database type before indexing the resource map.

Line 91 uses $database->getAttribute('type') without a default and immediately indexes Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType]. If older DB metadata lacks type (or has an unexpected value), this will trigger an undefined index and break the report. Consider defaulting to Resource::TYPE_DATABASE when the value is missing or not in the map.

🔧 Suggested fix
-            $databaseType = $database->getAttribute('type');
-            if (in_array($databaseType, [Resource::TYPE_DATABASE_LEGACY,Resource::TYPE_DATABASE_TABLESDB])) {
-                $databaseType = Resource::TYPE_DATABASE;
-            }
-
-            $databaseSpecificResources = Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType];
+            $databaseType = $database->getAttribute('type', Resource::TYPE_DATABASE);
+            if (\in_array($databaseType, [Resource::TYPE_DATABASE_LEGACY, Resource::TYPE_DATABASE_TABLESDB], true)) {
+                $databaseType = Resource::TYPE_DATABASE;
+            }
+            if (!isset(Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType])) {
+                $databaseType = Resource::TYPE_DATABASE;
+            }
+
+            $databaseSpecificResources = Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType];
🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite/Reader/Database.php` around lines 91 - 97, The
code reads $database->getAttribute('type') and immediately indexes
Resource::DATABASE_TYPE_RESOURCE_MAP[$databaseType], which can throw an
undefined index for missing/unexpected types; update the logic in the block
around $databaseType and $databaseSpecificResources to default to
Resource::TYPE_DATABASE when getAttribute('type') returns null/empty or when
$databaseType is not a key in Resource::DATABASE_TYPE_RESOURCE_MAP (also keep
the existing normalization for Resource::TYPE_DATABASE_LEGACY and
Resource::TYPE_DATABASE_TABLESDB), e.g. compute $databaseType with a fallback
and guard the lookup with isset/array_key_exists before assigning
$databaseSpecificResources so it never indexes an unknown key.

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

Caution

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

⚠️ Outside diff range comments (1)
src/Migration/Destinations/Appwrite.php (1)

796-813: Normalized dates computed but not used.

Lines 796-797 normalize the dates, but lines 811-812 use the raw resource values instead. This inconsistency could cause date format issues.

Proposed fix
         $index = new UtopiaDocument([
             '$id' => ID::custom($database->getSequence() . '_' . $table->getSequence() . '_' . $resource->getKey()),
             'key' => $resource->getKey(),
             'status' => 'available', // processing, available, failed, deleting, stuck
             'databaseInternalId' => $database->getSequence(),
             'databaseId' => $database->getId(),
             'collectionInternalId' => $table->getSequence(),
             'collectionId' => $table->getId(),
             'type' => $resource->getType(),
             'attributes' => $resource->getColumns(),
             'lengths' => $lengths,
             'orders' => $resource->getOrders(),
-            '$createdAt' => $resource->getCreatedAt(),
-            '$updatedAt' => $resource->getUpdatedAt(),
+            '$createdAt' => $createdAt,
+            '$updatedAt' => $updatedAt,
         ]);
🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 82-84: The PHPDoc parameter name for the callable is incorrect: it
documents $getDatabasesDSN but the actual parameter is named $getDatabaseDSN;
update the PHPDoc in src/Migration/Destinations/Appwrite.php so the param
annotation matches the real parameter name ($getDatabaseDSN) (or rename the
function parameter to $getDatabasesDSN if you prefer consistency), ensuring the
callable signature stays callable(string $databaseType):string and references
the correct symbol ($getDatabaseDSN) used in the class/method.
- Around line 352-357: The code duplicates the normalizeDateTime calls for
$createdAt and $updatedAt in the Appwrite migration (duplicate lines call
normalizeDateTime twice); remove the second pair of duplicate calls so
$createdAt and $updatedAt are only set once (keep the first calls that use
normalizeDateTime($resource->getCreatedAt()) and
normalizeDateTime($resource->getUpdatedAt(), $createdAt)), ensuring the
$updatedAt uses the $createdAt value from the initial normalization.
- Around line 973-994: The foreach currently declares an unused $value when
iterating $this->rowBuffer rows; change the inner loop to iterate keys only so
$value is not declared (e.g. replace foreach ($row as $key => $value) with a
keys-only iteration such as foreach (array_keys($row->getArrayCopy()) as $key)),
keeping the surrounding logic that checks $table->getAttribute('attributes', [])
and calls $row->removeAttribute($key) intact; this removes the unused $value
while preserving the orphan-attribute removal behavior tied to
$dbForDatabases->getAdapter()->getSupportForAttributes().
- Around line 429-434: The code duplicates calls to $this->normalizeDateTime for
$createdAt and $updatedAt in the Appwrite migration class; remove the second
pair of duplicate lines so only the first calls remain (keep the first
assignments: $createdAt = $this->normalizeDateTime($resource->getCreatedAt());
and $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(),
$createdAt);), ensuring the variables are assigned once in the method in class
Appwrite (look for the normalizeDateTime calls around the existing assignments
to $createdAt and $updatedAt).
- Line 15: Remove the unused import Dom\Document from the Appwrite migration
file: locate the top-level use statement "use Dom\Document" in the Appwrite
class and delete it (or replace it with the correct import if it was intended to
be used), ensuring no references to Document remain in the class (check methods
in class Appwrite) so the file no longer contains the unused import.
♻️ Duplicate comments (1)
src/Migration/Destinations/Appwrite.php (1)

471-476: Clarify DocumentsDB guard behavior and message.

The guard blocks both Column and Attribute resources for DocumentsDB, but the status message only mentions "Columns". Consider updating the message to accurately reflect the intended behavior:

Proposed fix
         if ($resource->getTable()->getDatabase()->getType() === Resource::TYPE_DATABASE_DOCUMENTSDB) {
-            $resource->setStatus(Resource::STATUS_SKIPPED, 'Columns not supported for DocumentsDB');
+            $resource->setStatus(Resource::STATUS_SKIPPED, 'Schema fields not supported for DocumentsDB');
             return false;
         }
🧹 Nitpick comments (1)
src/Migration/Destinations/Appwrite.php (1)

1534-1537: Unused variable $columnSize.

The variable $columnSize is extracted but never used. Either remove it or add a comment indicating it's reserved for future use.

Proposed fix (remove unused variable)
             $columnStatus = $oldColumns[$columnIndex]['status'];
             $columnType = $oldColumns[$columnIndex]['type'];
-            $columnSize = $oldColumns[$columnIndex]['size'];
             $columnArray = $oldColumns[$columnIndex]['array'] ?? false;

use Appwrite\Services\Storage;
use Appwrite\Services\Teams;
use Appwrite\Services\Users;
use Dom\Document;
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

Unused import detected.

Dom\Document is imported but not used anywhere in the file. This appears to be a leftover from refactoring.

Proposed fix
-use Dom\Document;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use Dom\Document;
🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` at line 15, Remove the unused import
Dom\Document from the Appwrite migration file: locate the top-level use
statement "use Dom\Document" in the Appwrite class and delete it (or replace it
with the correct import if it was intended to be used), ensuring no references
to Document remain in the class (check methods in class Appwrite) so the file no
longer contains the unused import.

Comment on lines +82 to +84
* @param UtopiaDatabase $dbForProject
* @param callable(UtopiaDocument $database):UtopiaDatabase $getDatabasesDB
* @param callable(string $databaseType):string $getDatabasesDSN
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

PHPDoc parameter name mismatch.

The PHPDoc shows $getDatabasesDSN but the actual parameter is $getDatabaseDSN (without the 's').

Proposed fix
-     * `@param` callable(string $databaseType):string $getDatabasesDSN
+     * `@param` callable(string $databaseType):string $getDatabaseDSN
🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 82 - 84, The PHPDoc
parameter name for the callable is incorrect: it documents $getDatabasesDSN but
the actual parameter is named $getDatabaseDSN; update the PHPDoc in
src/Migration/Destinations/Appwrite.php so the param annotation matches the real
parameter name ($getDatabaseDSN) (or rename the function parameter to
$getDatabasesDSN if you prefer consistency), ensuring the callable signature
stays callable(string $databaseType):string and references the correct symbol
($getDatabaseDSN) used in the class/method.

Comment on lines 352 to +357
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

$database = $this->database->createDocument('databases', new UtopiaDocument([
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

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

Duplicate normalizeDateTime calls.

Lines 355-356 duplicate the exact same normalization already performed on lines 352-353. This appears to be a copy-paste error.

Proposed fix
         $createdAt = $this->normalizeDateTime($resource->getCreatedAt());
         $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

-        $createdAt = $this->normalizeDateTime($resource->getCreatedAt());
-        $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
-
         $database = $this->dbForProject->createDocument('databases', new UtopiaDocument([
🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 352 - 357, The code
duplicates the normalizeDateTime calls for $createdAt and $updatedAt in the
Appwrite migration (duplicate lines call normalizeDateTime twice); remove the
second pair of duplicate calls so $createdAt and $updatedAt are only set once
(keep the first calls that use normalizeDateTime($resource->getCreatedAt()) and
normalizeDateTime($resource->getUpdatedAt(), $createdAt)), ensuring the
$updatedAt uses the $createdAt value from the initial normalization.

Comment on lines 429 to +434
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

$table = $this->database->createDocument('database_' . $database->getSequence(), new UtopiaDocument([
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

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

Duplicate normalizeDateTime calls.

Lines 432-433 duplicate the normalization already performed on lines 429-430.

Proposed fix
         $createdAt = $this->normalizeDateTime($resource->getCreatedAt());
         $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);

-        $createdAt = $this->normalizeDateTime($resource->getCreatedAt());
-        $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
-
         $dbForDatabases = ($this->getDatabasesDB)($database);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
$table = $this->database->createDocument('database_' . $database->getSequence(), new UtopiaDocument([
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
$dbForDatabases = ($this->getDatabasesDB)($database);
🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 429 - 434, The code
duplicates calls to $this->normalizeDateTime for $createdAt and $updatedAt in
the Appwrite migration class; remove the second pair of duplicate lines so only
the first calls remain (keep the first assignments: $createdAt =
$this->normalizeDateTime($resource->getCreatedAt()); and $updatedAt =
$this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);), ensuring the
variables are assigned once in the method in class Appwrite (look for the
normalizeDateTime calls around the existing assignments to $createdAt and
$updatedAt).

Comment on lines +973 to 994
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
foreach ($this->rowBuffer as $row) {
foreach ($row as $key => $value) {
if (\str_starts_with($key, '$')) {
continue;
}

/** @var \Utopia\Database\Document $attribute */
$found = false;
foreach ($table->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('key') == $key) {
$found = true;
break;
/** @var \Utopia\Database\Document $attribute */
$found = false;
foreach ($table->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('key') == $key) {
$found = true;
break;
}
}
}

if (! $found) {
$row->removeAttribute($key);
if (! $found) {
$row->removeAttribute($key);
}
}
}
}
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

Unused variable $value in foreach loop.

The $value variable is declared but never used. Only $key is needed for the orphan attribute check.

Proposed fix
                 if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
                     foreach ($this->rowBuffer as $row) {
-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {
                             if (\str_starts_with($key, '$')) {
                                 continue;
                             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
foreach ($this->rowBuffer as $row) {
foreach ($row as $key => $value) {
if (\str_starts_with($key, '$')) {
continue;
}
/** @var \Utopia\Database\Document $attribute */
$found = false;
foreach ($table->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('key') == $key) {
$found = true;
break;
/** @var \Utopia\Database\Document $attribute */
$found = false;
foreach ($table->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('key') == $key) {
$found = true;
break;
}
}
}
if (! $found) {
$row->removeAttribute($key);
if (! $found) {
$row->removeAttribute($key);
}
}
}
}
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
foreach ($this->rowBuffer as $row) {
foreach ($row as $key => $_) {
if (\str_starts_with($key, '$')) {
continue;
}
/** `@var` \Utopia\Database\Document $attribute */
$found = false;
foreach ($table->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('key') == $key) {
$found = true;
break;
}
}
if (! $found) {
$row->removeAttribute($key);
}
}
}
}
🧰 Tools
🪛 PHPMD (2.15.0)

975-975: Avoid unused local variables such as '$value'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 973 - 994, The foreach
currently declares an unused $value when iterating $this->rowBuffer rows; change
the inner loop to iterate keys only so $value is not declared (e.g. replace
foreach ($row as $key => $value) with a keys-only iteration such as foreach
(array_keys($row->getArrayCopy()) as $key)), keeping the surrounding logic that
checks $table->getAttribute('attributes', []) and calls
$row->removeAttribute($key) intact; this removes the unused $value while
preserving the orphan-attribute removal behavior tied to
$dbForDatabases->getAdapter()->getSupportForAttributes().

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/Migration/Destinations/Appwrite.php (2)

796-813: Use normalized timestamps for index metadata.

$createdAt/$updatedAt are computed but not used. If the raw resource values aren’t normalized, index creation can fail validation.

✅ Fix
-            '$createdAt' => $resource->getCreatedAt(),
-            '$updatedAt' => $resource->getUpdatedAt(),
+            '$createdAt' => $createdAt,
+            '$updatedAt' => $updatedAt,

570-598: Use the target database adapter for attribute validation.

The checkAttribute() call validates against $this->dbForProject, which is the project metadata store, not the target database adapter. Since the attribute definition includes adapter-specific fields (type, format, formatOptions, filters), validation should occur against $dbForDatabases to ensure compatibility with the target adapter's capabilities (e.g., vector support, spatial rules). Additionally, $dbForDatabases is already initialized on line 572 but remains unused, suggesting this is the intended validation point.

🔧 Proposed fix
-            $this->dbForProject->checkAttribute($table, $column);
+            $dbForDatabases->checkAttribute($table, $column);
🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 1539-1542: Remove the unused variable assignment $columnSize from
the code that reads column metadata (the block that assigns $columnStatus,
$columnType, $columnSize, $columnArray from $oldColumns[$columnIndex]);
specifically delete the line assigning $columnSize (or, if size is actually
needed, use $columnSize where appropriate), leaving only $columnStatus,
$columnType and $columnArray assignments to resolve the PHPMD unused-variable
warning.

Comment on lines +1539 to +1542
$columnStatus = $oldColumns[$columnIndex]['status'];
$columnType = $oldColumns[$columnIndex]['type'];
$columnSize = $oldColumns[$columnIndex]['size'];
$columnArray = $oldColumns[$columnIndex]['array'] ?? false;
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

Remove unused $columnSize.

$columnSize is assigned but never used (PHPMD).

🧹 Cleanup
-            $columnSize = $oldColumns[$columnIndex]['size'];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$columnStatus = $oldColumns[$columnIndex]['status'];
$columnType = $oldColumns[$columnIndex]['type'];
$columnSize = $oldColumns[$columnIndex]['size'];
$columnArray = $oldColumns[$columnIndex]['array'] ?? false;
$columnStatus = $oldColumns[$columnIndex]['status'];
$columnType = $oldColumns[$columnIndex]['type'];
$columnArray = $oldColumns[$columnIndex]['array'] ?? false;
🧰 Tools
🪛 PHPMD (2.15.0)

1541-1541: Avoid unused local variables such as '$columnSize'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 1539 - 1542, Remove the
unused variable assignment $columnSize from the code that reads column metadata
(the block that assigns $columnStatus, $columnType, $columnSize, $columnArray
from $oldColumns[$columnIndex]); specifically delete the line assigning
$columnSize (or, if size is actually needed, use $columnSize where appropriate),
leaving only $columnStatus, $columnType and $columnArray assignments to resolve
the PHPMD unused-variable warning.

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