Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Jan 19, 2026

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

Summary by CodeRabbit

  • New Features

    • Multi-database migration support: DocumentsDB and VectorDB added.
    • Many new attribute/field types: vectors, objects, enums, relationships, boolean, datetime, decimal, integer, email, ip, url, spatial types (point/line/polygon), and more.
    • Improved CSV import/export to handle richer data shapes and document/collection exports.
  • Changes

    • Migration tooling and readers updated to route resources per database type.

✏️ 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

This PR adds multi-database support across the migration system: new database resource types (DocumentsDB, VectorDB), Collection and Document resources, and mappings to treat collections/attributes alongside tables/columns. It introduces an abstract Attribute class with many concrete attribute implementations and corresponding Column types (object, vector). Appwrite source and destination were refactored to resolve per-database contexts via callables/readers and to use per-database DSNs. CSV and Reader implementations were updated to handle new database/entity/record/attribute flows. A single small change also appends database type to a resource cache key.

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.29% 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 does not clearly convey the scope or purpose of the substantial changes made in this PR. Consider using a more descriptive title that captures the main objective, such as 'Add multi-database type support (Tables, Documents, Vector)' or 'Implement multi-type database architecture'.
✅ 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 +459 to +464
protected function createField(Column|Attribute $resource): bool
{
if ($resource->getTable()->getDatabase()->getType() === Resource::TYPE_DATABASE_DOCUMENTSDB) {
$resource->setStatus(Resource::STATUS_SKIPPED, 'Columns not supported for DocumentsDB');
return 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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the createField method context and how it's used
cd src/Migration/Destinations && grep -n "createField" Appwrite.php | head -20

Repository: utopia-php/migration

Length of output: 198


🏁 Script executed:

# Check how DocumentsDB is handled elsewhere in the file
rg "TYPE_DATABASE_DOCUMENTSDB" src/Migration/Destinations/Appwrite.php -B 2 -A 2

Repository: utopia-php/migration

Length of output: 798


🏁 Script executed:

# Look for any other guards that differentiate between Column and Attribute
rg "instanceof Column|instanceof Attribute" src/Migration/Destinations/ -A 2 -B 2

Repository: utopia-php/migration

Length of output: 46


🏁 Script executed:

# Check if there are other methods that handle both Column and Attribute separately
rg "Column\|Attribute" src/Migration/Destinations/Appwrite.php -B 1 -A 5 | head -50

Repository: utopia-php/migration

Length of output: 386


🏁 Script executed:

# Look for any documentation or tests about DocumentsDB support
find . -type f \( -name "*.md" -o -name "*test*" -o -name "*Test*" \) -exec grep -l "DocumentsDB" {} \; 2>/dev/null | head -10

Repository: utopia-php/migration

Length of output: 46


🌐 Web query:

Appwrite DocumentsDB column support schema attributes

💡 Result:

Appwrite (TablesDB / DocumentsDB) column (attribute) schema supports the following core properties and types.

Core column properties

  • key (string) — column identifier. [1]
  • type (string) — data type (see list below). [1][2]
  • size (integer) — required for string types. [2]
  • required (boolean) — whether value must be provided. [1][2]
  • default — default value when not provided (some types forbid default when required). [1][2]
  • array (boolean) — allow array of values. [2]
  • type-specific options — e.g. enum values, relationship target, spatial defaults, numeric signed/min/max, formats. [1][2]
  • indexes are defined separately (key, type: key/fulltext/unique/spatial, attributes, orders, lengths). [2]

Common column/attribute types (supported by Appwrite Databases / TablesDB)

  • string (with size, format options) [2]
  • integer (signed/unsigned, min/max) [2]
  • float (signed, min/max) [2]
  • boolean (default) [2]
  • datetime / timestamp [2]
  • enum (allowed values) [6]
  • email, url, ip (special formats/validators) [6]
  • relationship (references another collection/table; one-to-one, one-to-many, two‑way, cascade options) [5][6]
  • spatial/geometric types: point, line, polygon (with defaults / coordinate arrays) [1]
  • encrypted string (server-side encrypted attribute — non-queryable) [3]
  • auto-increment / sequence columns (automatic $sequence support) [3]

Notes

  • The newer TablesDB terminology maps Collections → Tables, Documents → Rows, Attributes → Columns; API endpoints and docs use TablesDB for newer features. [1][2]
  • Some operations (adding/removing attributes) are processed asynchronously for existing data in older APIs; newer TablesDB APIs include synchronous full-schema creation features. [1][2]

Sources

  • Appwrite TablesDB API reference (columns examples: point/line, create table payload with column fields). [1][2]
  • Appwrite docs — Create Attributes / Attribute types (string, integer, float, boolean, enum, ip, email, url, relationship). [6]
  • Appwrite changelog (auto-increment, encrypted string attribute announcements). [3]

DocumentsDB guard skips both Column and Attribute creation, but message only references Columns.

The error message "Columns not supported for DocumentsDB" suggests the guard should apply only to Column resources, yet it blocks both Column and Attribute. Clarify whether both resource types should be skipped for DocumentsDB, or update the guard and message to reflect the intended behavior.

🤖 Prompt for AI Agents
In `@src/Migration/Destinations/Appwrite.php` around lines 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).

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.

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