Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Oct 7, 2025

Summary by CodeRabbit

  • New Features

    • Added DocumentsDB & VectorDB support (databases, collections, documents) and new resource types.
    • Per-project and per-database export/import/report flows with database-aware readers and mappings.
    • New attribute/column types (object, vector, geometric, typed attributes) and collection/document resources.
    • CSV export/import now supports document records and database-aware row parsing.
  • Bug Fixes

    • Guarded unsupported attribute/field operations for DocumentsDB to avoid failures.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Adds per-project and per-database contexts to source and destination Appwrite adapters via callables, replacing many global $database usages with $dbForProject and per-database resolvers. Introduces new database resource types (DocumentsDB, VectorDB, legacy/tables mappings), new Collection/Document/Database models (with database/DSN fields), many Attribute and Column classes with fromArray factories, per-database Reader routing and a new reader API method getSupportForAttributes(). Updates Transfer grouping, cache key composition (includes database type), CSV/source reader wiring, and constructor signatures to accept database resolvers. Numerous factories and mapping helpers added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Files/areas to pay extra attention to:

  • src/Migration/Destinations/Appwrite.php — constructor signature changes, widespread replacement of $this->database with $dbForProject/per-db resolution and renamed methods (createEntity/createField/createRecord).
  • src/Migration/Sources/Appwrite.php and src/Migration/Sources/Appwrite/Reader/Database.php — per-database reader factory, routing logic for list/count/query, and propagation of database metadata (enabled/originalId/type/database).
  • New resource and model classes (Resources/Database/, Attributes/, Columns/*) — validate fromArray shapes, defaults, and type mappings.
  • src/Migration/Resource.php and src/Migration/Transfer.php — new resource constants and grouping changes that affect public enums and extraction logic.
  • src/Migration/Cache.php — cache key composition now includes resource database type.
  • Public API/signature changes (constructors, Reader interface) — ensure backward compatibility where required.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'documents db + vector db migration' directly and clearly reflects the main objective: adding migration support for DocumentsDB and VectorDB database types alongside the existing TablesDB/legacy database support.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-documents-db

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.

@ArnabChatterjee20k ArnabChatterjee20k changed the title added database type for migrating database type documents db migration Oct 7, 2025
separated database object into dbForProject to interact with project metadata and dbForDatabase to work with the targetted database
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Migration/Destinations/Appwrite.php (1)

411-428: Consider extracting repeated database/table lookup pattern.

The sequence (fetch database document → fetch table document → invoke factory) appears in createTable, createColumn, createIndex, and createRow. Extracting this into a helper method would reduce duplication and centralize error handling.

Example helper:

private function getDbForDatabase(Database|string $databaseId): array
{
    $database = $this->dbForProject->getDocument(
        'databases',
        is_string($databaseId) ? $databaseId : $databaseId->getId()
    );
    
    if ($database->isEmpty()) {
        throw new Exception(
            message: 'Database not found',
        );
    }
    
    $dbForDatabase = call_user_func(
        $this->getDatabaseDB,
        $database->getAttribute('database')
    );
    
    return [$database, $dbForDatabase];
}

Then replace repeated blocks with:

[$database, $dbForDatabase] = $this->getDbForDatabase($resource->getDatabase()->getId());

Also applies to: 533-577, 734-877, 994-998

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d063f46 and 262c1c2.

📒 Files selected for processing (1)
  • src/Migration/Destinations/Appwrite.php (32 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (1)
src/Migration/Destinations/Appwrite.php (4)
src/Migration/Resources/Database/Database.php (2)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Sources/Appwrite.php (1)
  • __construct (70-103)
src/Migration/Resources/Database/Table.php (1)
  • getDatabase (91-94)
bin/MigrationCLI.php (1)
  • getDatabase (265-399)
🔇 Additional comments (3)
src/Migration/Destinations/Appwrite.php (3)

60-63: Property type is correct for callable storage.

The mixed type is appropriate here since callable cannot be used as a property type in PHP 8.1+. The docblock provides the precise signature.


78-101: Constructor correctly implements dual-handle pattern.

The refactoring from a single database handle to separate $dbForProject (promoted property) and a factory callable is architecturally sound:

  • Project-level metadata operations use $dbForProject
  • Database-specific operations obtain handles on-demand via the factory
  • The callable cannot be promoted due to PHP type system limitations, so line 100 assignment is necessary

341-351: New 'database' field correctly propagates database context.

Adding the 'database' field (line 350) enables downstream operations to obtain the appropriate database handle via the factory. This aligns with the broader PR objective to support per-database routing.

}

$table = $this->database->createDocument('database_' . $database->getSequence(), new UtopiaDocument([
$dbForDatabase = call_user_func($this->getDatabaseDB, $database->getAttribute('database'));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify factory handles missing or invalid database identifiers.

The factory is invoked with $database->getAttribute('database') without checking if the attribute exists or is valid. If getAttribute('database') returns null, an empty string, or an invalid identifier, the factory might fail unexpectedly.

Run the following script to check if there are safeguards elsewhere in the codebase for the 'database' attribute:

Also applies to: 533-533, 734-734, 994-994


🏁 Script executed:

#!/bin/bash
# Description: Verify that the 'database' attribute is always set and validated.

# Search for Database resource creation to see if 'database' is always provided
rg -nP -A 5 'new Database\(' --type=php

# Search for validation or null checks on the 'database' attribute
rg -nP "getAttribute\('database'\)" --type=php -C 3

Length of output: 8320


Validate database identifier before invoking factory
Ensure $database->getAttribute('database') returns a non-null, valid identifier before calling $this->getDatabaseDB; add a guard or fallback to avoid runtime failures. Applies to lines 411, 533, 734 and 994 in src/Migration/Destinations/Appwrite.php.

🤖 Prompt for AI Agents
In src/Migration/Destinations/Appwrite.php around lines 411, 533, 734 and 994,
the calls pass $database->getAttribute('database') directly into the factory
($this->getDatabaseDB) without validating the identifier; add a guard that reads
the attribute into a local variable, verify it's non-null, a non-empty string
(or valid expected type/format), and only call
call_user_func($this->getDatabaseDB, $id) when valid; if invalid, handle
explicitly (throw a clear InvalidArgumentException or log and skip/fallback to a
default DB) so you avoid runtime failures and make the error path deterministic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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)

82-109: Fix parameter order: required callable after optional triggers PHP 8.1 deprecation.

PHP 8.1 emits a deprecation (and future versions will reject it) when a required parameter ($getDatabaseDB) follows an optional one ($dbForProject = null). The pipeline already fails on this. Reorder the parameters or give the callable a default so that required parameters precede optional ones.

Apply this diff:

-        protected ?UtopiaDatabase $dbForProject = null,
-        callable $getDatabaseDB
+        protected ?UtopiaDatabase $dbForProject = null,
+        ?callable $getDatabaseDB = null

and keep the runtime validation in Lines 99‑105 to enforce the presence of the callable only for SOURCE_DATABASE, adjusting property/default handling accordingly.

🧹 Nitpick comments (2)
src/Migration/Resources/Database/Document.php (1)

1-13: Add #[Override] attribute for consistency.

The Document class extends Row and returns Resource::TYPE_DOCUMENT from getName(), providing backward compatibility for the legacy Document terminology. However, it's missing the #[Override] attribute that Collection.php uses for the same pattern.

For consistency with Collection.php, consider adding the #[Override] attribute:

+use Override;
+
 use Utopia\Migration\Resource;
 
 class Document extends Row
 {
+    #[Override]
     public static function getName(): string
     {
         return Resource::TYPE_DOCUMENT;
     }
 }

Based on learnings.

src/Migration/Resources/Database/DocumentsDB.php (1)

1-13: Add #[Override] attribute for consistency.

The DocumentsDB class extends Database and returns Resource::TYPE_DOCUMENTSDB_DATABASE from getName(). For consistency with Collection.php, consider adding the #[Override] attribute to make the override explicit.

Apply this diff:

+use Override;
+
 use Utopia\Migration\Resource;
 
 class DocumentsDB extends Database
 {
+    #[Override]
     public static function getName(): string
     {
         return Resource::TYPE_DOCUMENTSDB_DATABASE;
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f072649 and b79066f.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • composer.json (1 hunks)
  • src/Migration/Destinations/Appwrite.php (33 hunks)
  • src/Migration/Resource.php (2 hunks)
  • src/Migration/Resources/Database/Collection.php (1 hunks)
  • src/Migration/Resources/Database/Document.php (1 hunks)
  • src/Migration/Resources/Database/DocumentsDB.php (1 hunks)
  • src/Migration/Sources/Appwrite.php (16 hunks)
  • src/Migration/Sources/Appwrite/Reader.php (1 hunks)
  • src/Migration/Sources/Appwrite/Reader/API.php (1 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Migration/Destinations/Appwrite.php
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
🧬 Code graph analysis (7)
src/Migration/Sources/Appwrite/Reader.php (2)
src/Migration/Sources/Appwrite/Reader/API.php (1)
  • getSupportForAttributes (244-247)
src/Migration/Sources/Appwrite/Reader/Database.php (1)
  • getSupportForAttributes (416-419)
src/Migration/Sources/Appwrite/Reader/Database.php (2)
src/Migration/Sources/Appwrite/Reader.php (1)
  • getSupportForAttributes (109-109)
src/Migration/Sources/Appwrite/Reader/API.php (1)
  • getSupportForAttributes (244-247)
src/Migration/Resources/Database/Collection.php (2)
src/Migration/Resource.php (2)
  • Resource (5-239)
  • getName (111-111)
src/Migration/Resources/Database/Table.php (1)
  • Table (8-110)
src/Migration/Sources/Appwrite/Reader/API.php (2)
src/Migration/Sources/Appwrite/Reader.php (1)
  • getSupportForAttributes (109-109)
src/Migration/Sources/Appwrite/Reader/Database.php (1)
  • getSupportForAttributes (416-419)
src/Migration/Resources/Database/Document.php (2)
src/Migration/Resource.php (2)
  • Resource (5-239)
  • getName (111-111)
src/Migration/Resources/Database/Row.php (1)
  • Row (8-98)
src/Migration/Resources/Database/DocumentsDB.php (1)
src/Migration/Resource.php (2)
  • Resource (5-239)
  • getName (111-111)
src/Migration/Sources/Appwrite.php (9)
src/Migration/Sources/Appwrite/Reader/Database.php (13)
  • Database (21-431)
  • report (27-109)
  • queryLimit (411-414)
  • queryEqual (369-372)
  • queryCursorAfter (380-409)
  • listDatabases (111-114)
  • listTables (116-155)
  • listColumns (157-217)
  • listIndexes (219-264)
  • getSupportForAttributes (416-419)
  • querySelect (359-362)
  • listRows (266-314)
  • getRow (316-353)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getDatabaseName (84-87)
  • getType (94-97)
  • fromArray (45-57)
src/Migration/Resources/Database/Document.php (2)
  • Document (7-13)
  • getName (9-12)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/DocumentsDB.php (1)
  • DocumentsDB (7-13)
src/Migration/Sources/Appwrite/Reader.php (12)
  • report (21-21)
  • queryLimit (108-108)
  • queryEqual (92-92)
  • queryCursorAfter (100-100)
  • listDatabases (29-29)
  • listTables (38-38)
  • listColumns (47-47)
  • listIndexes (56-56)
  • getSupportForAttributes (109-109)
  • querySelect (83-83)
  • listRows (65-65)
  • getRow (75-75)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Resources/Database/Table.php (4)
  • getDatabase (91-94)
  • Table (8-110)
  • getTableName (96-99)
  • getRowSecurity (101-104)
src/Migration/Resources/Database/Row.php (3)
  • getTable (86-89)
  • fromArray (53-61)
  • Row (8-98)
🪛 GitHub Actions: CodeQL
src/Migration/Sources/Appwrite.php

[error] 83-83: Deprecated in PHP 8.1: Required parameter $getDatabaseDB follows optional parameter $dbForProject.

🪛 PHPMD (2.15.0)
src/Migration/Sources/Appwrite.php

123-132: Avoid unused private methods such as 'createReaderForDatabase'. (undefined)

(UnusedPrivateMethod)

🔇 Additional comments (7)
src/Migration/Sources/Appwrite/Reader.php (1)

109-110: LGTM: Interface expansion for attribute support.

The addition of getSupportForAttributes(): bool expands the Reader interface to expose attribute support capabilities. This is a breaking change, but the context shows that both implementations (API and Database readers) have been updated accordingly.

src/Migration/Resources/Database/Collection.php (1)

1-15: LGTM: Legacy resource type support.

The Collection class extends Table and returns Resource::TYPE_COLLECTION from getName(), providing backward compatibility for the legacy Collection terminology. This aligns with the codebase's pattern of supporting both current (Table/Column/Row) and legacy (Collection/Attribute/Document) terminology during the migration period.

Based on learnings.

src/Migration/Resource.php (2)

33-34: LGTM: New resource type constant added.

The TYPE_DOCUMENTSDB_DATABASE constant follows the existing naming pattern and is properly defined.


75-75: LGTM: Resource type registered.

The new TYPE_DOCUMENTSDB_DATABASE constant is correctly added to the ALL_RESOURCES array, ensuring it's recognized throughout the system.

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

244-247: LGTM: Interface implementation for attribute support.

The getSupportForAttributes() method correctly implements the Reader interface, returning true to indicate that the API reader supports attribute operations.

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

416-419: LGTM: Capability delegation to adapter.

The getSupportForAttributes() method correctly implements the Reader interface by delegating to the underlying database adapter's getSupportForAttributes() method, allowing adapter-specific capability reporting.

composer.json (1)

29-29: Revert to stable version before merging
Confirm the feat-documentsdb branch has been merged and tagged as a stable release (e.g., 1.4.5), then update composer.json to reference that version instead of the dev alias.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

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

64-71: Include the new $database field in serialization.

We now persist a $database identifier, but jsonSerialize() still omits it. Any cache/export round-trip will drop the value, and subsequent fromArray() calls will fail the new constructor argument. Please add 'database' => $this->database to the payload.

Apply this diff:

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

50-61: Avoid new static() to satisfy CodeQL.

The late-static binding here causes the same “unsafe dynamic object instantiation” failure flagged by the CodeQL pipeline. Please revert to a concrete instantiation (e.g. new self(...)) and let subclasses such as Collection override fromArray() explicitly, or introduce a small factory that instantiates only vetted subclasses. This removes the security warning while still supporting the new resources.

src/Migration/Resources/Database/Row.php (1)

53-60: Replace new static() to clear the CodeQL failure.

Just like the other resources, this new static is tripping the pipeline’s security rule. Please refactor to a concrete instantiation (new self) and, if needed, override fromArray() in subclasses such as Document so they construct their own instances. That keeps the behaviour while satisfying CodeQL.

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

1-1: Fix PSR-12 formatting violation.

The linter detected a single_space_around_construct violation. Ensure there's exactly one space around language constructs (e.g., if, foreach, while).

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

1-1: Fix PSR-12 formatting violation.

The linter detected a single_space_around_construct violation. Ensure proper spacing around language constructs.

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

1-1: Fix PSR-12 formatting violation.

The linter detected a single_space_around_construct violation. Ensure proper spacing around language constructs.

♻️ Duplicate comments (3)
src/Migration/Destinations/Appwrite.php (1)

413-413: Validate database identifier before invoking factory.

As flagged in a previous review, $database->getAttribute('database') is passed directly to $this->getDatabaseDB without validation. If the attribute is missing, null, or empty, the factory might fail unexpectedly.

Refer to the past review comment for the recommended fix: extract the attribute, validate it's non-null and non-empty, then call the factory. Apply this pattern at lines 413, 541, 742, and 977.

Also applies to: 541-541, 742-742, 977-977

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

791-804: Pass database type, not database name, to getEntity().

As flagged in a previous review, getEntity() expects the database type (e.g., Resource::TYPE_DOCUMENTSDB_DATABASE) to decide between Collection::fromArray() and Table::fromArray(), but here we pass $databaseName. This causes Document DB tables to be emitted as Table resources.

Apply this diff:

-                    $newTable = self::getEntity($databaseName, [
+                    $newTable = self::getEntity($database->getType(), [

Refer to the past review comment for additional context.


1189-1205: Pass database type, not database name, to getRecord().

Similar to the issue above, getRecord() expects the database type to distinguish between Document::fromArray() and Row::fromArray(), but here we pass $table->getDatabase()->getDatabaseName(). This causes Documents DB rows to be emitted as Row resources.

Apply this diff:

-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [

Refer to the past review comment for details.

🧹 Nitpick comments (3)
src/Migration/Sources/Appwrite/Reader/Database.php (2)

90-90: Address TODO: optimize table counting.

The comment "todo: replacing this with a single list tables call downwards with +1 in each table" suggests a performance optimization opportunity. Consider whether this should be addressed now or tracked separately.

Would you like me to generate a refactored version that uses a single listTables call with a count query, or should this be tracked as a separate issue?


129-133: Clarify TODO: attribute support check.

The comment "todo: apply check get support for attributes" is unclear. If this refers to conditionally counting attributes based on database capabilities, please clarify the intent.

Do you want me to investigate what this TODO should accomplish and propose an implementation?

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

123-132: createReaderForDatabase method appears unused.

PHPMD flags createReaderForDatabase() as unused. If this method is intended for future use or external invocation, consider marking it protected or adding a comment explaining its purpose. Otherwise, remove it to reduce maintenance burden.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b79066f and 8ddf9a2.

📒 Files selected for processing (8)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
  • src/Migration/Resource.php (3 hunks)
  • src/Migration/Resources/Database/Database.php (3 hunks)
  • src/Migration/Resources/Database/Row.php (1 hunks)
  • src/Migration/Resources/Database/Table.php (1 hunks)
  • src/Migration/Sources/Appwrite.php (17 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (9 hunks)
  • src/Migration/Transfer.php (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-06-28T09:45:36.026Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Row.php:60-60
Timestamp: 2025-06-28T09:45:36.026Z
Learning: In the utopia-php/migration codebase, the `fromArray` method is not used on Row objects, so mismatches between `jsonSerialize()` output keys and `fromArray()` input expectations for Row class are not problematic.

Applied to files:

  • src/Migration/Resources/Database/Row.php
  • src/Migration/Resources/Database/Table.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (5)
src/Migration/Transfer.php (1)
src/Migration/Resource.php (1)
  • Resource (5-257)
src/Migration/Resources/Database/Database.php (4)
src/Migration/Resources/Database/Row.php (1)
  • fromArray (53-61)
src/Migration/Resources/Database/Table.php (2)
  • fromArray (50-62)
  • getDatabase (91-94)
src/Migration/Sources/Appwrite.php (1)
  • getDatabase (1787-1795)
src/Migration/Sources/Appwrite/Reader/Database.php (1)
  • getDatabase (40-47)
src/Migration/Sources/Appwrite/Reader/Database.php (9)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/Document.php (1)
  • Document (7-13)
src/Migration/Destinations/Appwrite.php (1)
  • __construct (78-101)
src/Migration/Resources/Database/Row.php (1)
  • __construct (16-24)
src/Migration/Resources/Database/Table.php (2)
  • __construct (20-32)
  • getDatabase (91-94)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1787-1795)
  • report (190-223)
src/Migration/Resource.php (1)
  • Resource (5-257)
src/Migration/Sources/Appwrite/Reader.php (2)
  • report (21-21)
  • getSupportForAttributes (109-109)
src/Migration/Destinations/Appwrite.php (7)
src/Migration/Resources/Database/Database.php (3)
  • __construct (21-32)
  • getDatabase (99-102)
  • getName (74-77)
src/Migration/Resources/Database/Row.php (4)
  • __construct (16-24)
  • getTable (86-89)
  • Row (8-98)
  • getName (76-79)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1787-1795)
  • getName (111-114)
src/Migration/Sources/Appwrite/Reader/Database.php (2)
  • __construct (30-35)
  • getDatabase (40-47)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
src/Migration/Resources/Database/Table.php (2)
  • getDatabase (91-94)
  • Table (8-110)
src/Migration/Sources/Appwrite.php (9)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-491)
  • report (49-142)
  • queryLimit (463-466)
  • queryEqual (408-411)
  • queryCursorAfter (419-461)
  • listDatabases (144-147)
  • getDatabase (40-47)
  • listTables (149-188)
  • listColumns (190-250)
  • listIndexes (252-297)
  • getSupportForAttributes (468-471)
  • querySelect (398-401)
  • listRows (299-350)
  • getRow (352-392)
src/Migration/Resources/Database/Document.php (2)
  • Document (7-13)
  • getName (9-12)
src/Migration/Resource.php (3)
  • Resource (5-257)
  • getName (129-129)
  • getId (148-151)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/DocumentsDB.php (1)
  • DocumentsDB (7-13)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Resources/Database/Table.php (5)
  • getDatabase (91-94)
  • Table (8-110)
  • getTableName (96-99)
  • getRowSecurity (101-104)
  • fromArray (50-62)
src/Migration/Resources/Database/Row.php (3)
  • getTable (86-89)
  • fromArray (53-61)
  • Row (8-98)
🪛 GitHub Actions: CodeQL
src/Migration/Resources/Database/Row.php

[error] 55-55: Unsafe usage of new static().

src/Migration/Resources/Database/Database.php

[error] 47-47: Unsafe usage of new static().

src/Migration/Resources/Database/Table.php

[error] 52-52: Unsafe usage of new static().

src/Migration/Sources/Appwrite.php

[error] 83-83: Deprecated in PHP 8.1: Required parameter $getDatabaseDB follows optional parameter $dbForProject.


[error] 748-748: PHPDoc tag @param references unknown parameter: $databases


[error] 1718-1718: PHPDoc tag @param references unknown parameter: $documentsDBDatabases

🪛 GitHub Actions: Linter
src/Migration/Resource.php

[error] 1-1: PSR-12: no_whitespace_in_blank_line violation detected. (File: src/Migration/Resource.php)

src/Migration/Sources/Appwrite/Reader/Database.php

[error] 1-1: PSR-12: single_space_around_construct violation detected. (File: src/Migration/Sources/Appwrite/Reader/Database.php)

src/Migration/Destinations/Appwrite.php

[error] 1-1: PSR-12: single_space_around_construct violation detected. (File: src/Migration/Destinations/Appwrite.php)

src/Migration/Sources/Appwrite.php

[error] 1-1: PSR-12: single_space_around_construct violation detected. (File: src/Migration/Sources/Appwrite.php)

🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

src/Migration/Sources/Appwrite.php

123-132: Avoid unused private methods such as 'createReaderForDatabase'. (undefined)

(UnusedPrivateMethod)


677-677: Avoid unused parameters such as '$resources'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (16)
src/Migration/Sources/Appwrite/Reader/Database.php (7)

25-35: Constructor accepts optional per-database factory callable.

The addition of ?callable $getDatabaseDB = null enables per-database resolution for multi-database contexts. The implementation correctly stores and uses this callable.


40-47: Database resolution helper correctly implements fallback.

The getDatabase() method properly routes to per-database instances when a DatabaseResource is provided and the factory is available, falling back to dbForProject otherwise. This design supports both legacy single-database and new multi-database workflows.


331-335: Correctly routes row queries to per-database instance.

The change from $this->dbForProject->find() to $dbInstance = $this->getDatabase($resource->getDatabase()); $dbInstance->find() properly routes row data operations to the appropriate database context.


425-461: Extended cursor handling for Collection and Document resources.

The addition of CollectionResource and DocumentResource cases in queryCursorAfter() correctly handles the new resource types. The logic appropriately distinguishes between metadata (stored in dbForProject) and per-database data (retrieved via getRow()).


468-471: New method delegates attribute support check to adapter.

The getSupportForAttributes() method correctly delegates to the underlying database adapter, enabling callers to determine whether the database supports schema-based attributes.


480-490: Per-database resource counting with proper routing.

The refactored countResources() method correctly routes counts to the appropriate database instance when a DatabaseResource is provided, while falling back to dbForProject for metadata tables. This aligns with the dual-context architecture.


78-83: Database type normalization is correct The normalization of TYPE_DATABASE_LEGACY and TYPE_DATABASE_TABLESDB to TYPE_DATABASE aligns with the keys in DATABASE_TYPE_RESOURCE_MAP and the isSupported logic, so resource filtering remains unaffected.

src/Migration/Destinations/Appwrite.php (3)

60-63: Constructor properly accepts per-database factory.

The updated constructor signature correctly accepts $dbForProject, $getDatabaseDB, and stores the callable for later use. The docblock annotations are clear and accurate.

Also applies to: 78-101


121-121: DocumentsDB resource type added to supported resources.

The addition of Resource::TYPE_DOCUMENTSDB_DATABASE correctly extends the destination's capabilities to handle Documents DB migrations.


982-1003: Orphan attribute filtering logic is correct but complex.

The new logic filters out attributes not present in the table's schema when getSupportForAttributes() returns true. This prevents errors when importing data with orphaned columns. The implementation correctly iterates through each row and removes unrecognized keys.

Note: PHPMD flags $value as unused on line 984, but this is a false positive—the loop iterates over $row to access keys, not values.

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

69-72: Constructor correctly accepts and validates getDatabaseDB callable.

The constructor properly stores the getDatabaseDB callable and validates that it's non-null when $source === SOURCE_DATABASE. This ensures the required dependencies are available for database-based sources.

Also applies to: 77-109


128-128: Verify getDatabaseDB callable handles null database parameter.

In createReaderForDatabase(), the code calls call_user_func($this->getDatabaseDB, new UtopiaDocument(['database' => $database])), but $database is Database|null. If null, the callable receives ['database' => null], which might not be valid.

Ensure the factory callable gracefully handles a null database key, or guard this call to only invoke the factory when $database is non-null.


158-158: DocumentsDB resource type added to supported resources.

The addition of Resource::TYPE_DOCUMENTSDB_DATABASE correctly extends the source's capabilities for Documents DB migrations.


299-307: reportDatabases correctly instantiates reader for metadata access.

The use of a reader (API or Database) with $this->dbForProject for reporting is appropriate, as the report logic queries metadata across all databases.


646-669: exportGroupDatabases split by database type.

The refactored logic correctly separates exports into exportTablesDB() for legacy/tablesdb databases and exportDocumentsDB() for Documents DB. This aligns with the new per-database-type resource mapping.


1787-1795: Helper methods correctly map database types to resource classes.

The new getDatabase(), getEntity(), and getRecord() helpers properly switch between Documents DB resources (DocumentsDB, Collection, Document) and legacy/tablesdb resources (Database, Table, Row) based on the database type. This centralizes the mapping logic.

Also applies to: 1815-1823, 1854-1862

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (8)
src/Migration/Destinations/Appwrite.php (2)

413-413: Validate database identifier before factory invocation.

Similar to a previous review comment, the code passes $database to the factory without validating that $database->getAttribute('database') is non-null/non-empty. Consider adding validation before the factory call.


447-447: Use constant instead of magic string.

As flagged in a previous review, use Resource::TYPE_DOCUMENTSDB_DATABASE instead of the hardcoded string 'documents' for consistency and maintainability.

Apply this diff:

-        if ($resource->getTable()->getDatabase()->getType() === 'documents') {
+        if ($resource->getTable()->getDatabase()->getType() === Resource::TYPE_DOCUMENTSDB_DATABASE) {
src/Migration/Sources/Appwrite.php (5)

77-109: Fix deprecated parameter order for PHP 8.1+ compatibility.

The pipeline correctly flags that required parameter $getDatabaseDB follows optional parameter $dbForProject, which is deprecated in PHP 8.1 and will cause a fatal error in PHP 9.0.

Reorder parameters to place all required parameters before optional ones:

     public function __construct(
         protected string $project,
         protected string $endpoint,
         protected string $key,
+        callable $getDatabaseDB,
         protected string $source = self::SOURCE_API,
-        protected ?UtopiaDatabase $dbForProject = null,
-        callable $getDatabaseDB
+        protected ?UtopiaDatabase $dbForProject = null
     ) {

Important: Update all call sites to match the new parameter order.


734-740: Fix PHPDoc: remove unknown parameter.

The pipeline correctly flags that PHPDoc references non-existent parameter $databases. Remove this line from the docblock.

     /**
      * @param string $databaseName
      * @param int $batchSize
-     * @param array<Resource> $databases
      * @throws Exception
      */

1170-1186: Pass database type instead of database name to getRecord().

The code passes $table->getDatabase()->getDatabaseName() to getRecord(), but getRecord() expects the database type to determine whether to create a Document or Row resource.

For DocumentsDB databases, this will incorrectly create Row resources instead of Document resources.

-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [
                         'id' => $id,

1692-1699: Fix PHPDoc: remove unknown parameter.

The pipeline correctly flags that PHPDoc references non-existent parameter $documentsDBDatabases. Remove this line from the docblock.

     /**
      * Export DocumentsDB databases (collections and documents)
      * 
      * @param int $batchSize
      * @param array $resources
-     * @param array $documentsDBDatabases
      */

779-792: Pass database type instead of database name to getEntity().

The code passes $databaseName (which is Database::getName() or DocumentsDB::getName()) to getEntity(), but getEntity() expects the database type to determine whether to create a Table or Collection resource.

For DocumentsDB databases, this will incorrectly create Table resources instead of Collection resources.

                 foreach ($response as $table) {
-                    $newTable = self::getEntity($databaseName, [
+                    $newTable = self::getEntity($database->getType(), [
                         'id' => $table['$id'],
                         'name' => $table['name'],

Based on learnings from past reviews, this maintains compatibility while correctly routing DocumentsDB tables to Collection resources.

src/Migration/Resource.php (1)

33-38: Past PSR-12 whitespace issue may still need attention.

A previous review flagged that the blank lines between these constants contain stray spaces, violating the PSR-12 no_whitespace_in_blank_line rule. Ensure blank lines 34 and 36 are truly empty (no spaces or tabs).

🧹 Nitpick comments (2)
src/Migration/Sources/Appwrite/Reader/Database.php (1)

40-47: Verify database identifier is valid before factory invocation.

The code passes $databaseResource->getDatabase() directly to the factory without validating it's non-null or non-empty. If getDatabase() returns an invalid identifier, the factory might fail unexpectedly.

Consider adding validation:

 private function getDatabase(?DatabaseResource $databaseResource = null): UtopiaDatabase
 {
     if ($this->getDatabaseDB !== null && $databaseResource !== null) {
+        $databaseId = $databaseResource->getDatabase();
+        if (empty($databaseId)) {
+            return $this->dbForProject;
+        }
         return call_user_func($this->getDatabaseDB, new UtopiaDocument(['database' => $databaseResource->getDatabase()]));
     }

     return $this->dbForProject;
 }
src/Migration/Destinations/Appwrite.php (1)

541-706: Consider caching the per-database instance.

The method calls call_user_func($this->getDatabaseDB, $database) multiple times (lines 541, 584, 644, 652, 670, 702). Consider caching the result in a local variable at the start of the method to improve efficiency and readability.

Example:

$dbForDatabase = call_user_func($this->getDatabaseDB, $database);
// Then use $dbForDatabase throughout instead of repeated calls
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ddf9a2 and 8bc1eb8.

📒 Files selected for processing (4)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
  • src/Migration/Resource.php (3 hunks)
  • src/Migration/Sources/Appwrite.php (17 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (9 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/Appwrite.php
🧬 Code graph analysis (3)
src/Migration/Destinations/Appwrite.php (3)
src/Migration/Sources/Appwrite.php (2)
  • __construct (77-109)
  • getDatabase (1768-1776)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (468-471)
src/Migration/Resource.php (1)
  • Resource (5-257)
src/Migration/Sources/Appwrite/Reader/Database.php (10)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/Document.php (1)
  • Document (7-13)
src/Migration/Destinations/Appwrite.php (1)
  • __construct (78-101)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1768-1776)
  • report (187-220)
src/Migration/Resources/Database/Row.php (1)
  • __construct (16-24)
src/Migration/Resources/Database/Table.php (2)
  • __construct (20-32)
  • getDatabase (91-94)
src/Migration/Sources/Appwrite/Reader/API.php (3)
  • __construct (19-23)
  • report (28-118)
  • getSupportForAttributes (244-247)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • isSupported (133-146)
src/Migration/Sources/Appwrite/Reader.php (2)
  • report (21-21)
  • getSupportForAttributes (109-109)
src/Migration/Sources/Appwrite.php (9)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-491)
  • report (49-142)
  • queryLimit (463-466)
  • queryEqual (408-411)
  • queryCursorAfter (419-461)
  • listDatabases (144-147)
  • getDatabase (40-47)
  • listTables (149-188)
  • listColumns (190-250)
  • listIndexes (252-297)
  • getSupportForAttributes (468-471)
  • querySelect (398-401)
  • listRows (299-350)
  • getRow (352-392)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Resources/Database/Document.php (2)
  • Document (7-13)
  • getName (9-12)
src/Migration/Resource.php (4)
  • Resource (5-257)
  • getName (129-129)
  • isSupported (133-146)
  • getId (148-151)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/DocumentsDB.php (1)
  • DocumentsDB (7-13)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Resources/Database/Table.php (4)
  • getDatabase (91-94)
  • Table (8-110)
  • getTableName (96-99)
  • fromArray (50-62)
src/Migration/Resources/Database/Row.php (3)
  • getTable (86-89)
  • fromArray (53-61)
  • Row (8-98)
🪛 GitHub Actions: CodeQL
src/Migration/Sources/Appwrite.php

[error] 83-83: Deprecated in PHP 8.1: Required parameter $getDatabaseDB follows optional parameter $dbForProject.


[error] 740-740: PHPDoc tag @param references unknown parameter: $databases


[error] 1699-1699: PHPDoc tag @param references unknown parameter: $documentsDBDatabases

🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (13)
src/Migration/Resource.php (1)

97-109: LGTM! Clear database type mappings.

The DATABASE_TYPE_RESOURCE_MAP provides a clean separation between TablesDB (table/column/row) and DocumentsDB (collection/document/attribute) resource types. The structure supports the per-database architecture introduced in this PR.

src/Migration/Sources/Appwrite/Reader/Database.php (4)

76-122: LGTM! Robust per-database reporting logic.

The report method correctly:

  • Normalizes legacy database types to TYPE_DATABASE
  • Uses DATABASE_TYPE_RESOURCE_MAP for database-specific resource types
  • Passes DatabaseResource context to countResources for accurate per-database counts

331-335: LGTM! Correct per-database context usage.

Both listRows() and getRow() correctly:

  • Fetch metadata (database/table info) from dbForProject
  • Use getDatabase() to obtain the per-database instance for actual row data operations

This separation ensures metadata queries use the project database while row data uses the appropriate database instance.

Also applies to: 384-391


425-455: LGTM! Clear resource type routing with helpful comments.

The queryCursorAfter() method correctly routes different resource types:

  • Metadata resources (Database, Table, Column, Index) → dbForProject
  • Data resources (Row, Document) → getRow() which uses per-database context

The inline comments explaining the routing are helpful for maintainability.


468-490: LGTM! Correct adapter delegation and per-database counting.

Both methods implement correct logic:

  • getSupportForAttributes() delegates to the project database adapter
  • countResources() routes to per-database instance when DatabaseResource is provided, otherwise uses dbForProject for metadata
src/Migration/Destinations/Appwrite.php (4)

78-101: LGTM! Constructor accepts per-database factory.

The constructor correctly accepts both $dbForProject and the $getDatabaseDB callable, storing the factory for later use in database-specific operations.


120-121: LGTM! DocumentsDB added to supported resources.

Correctly adds Resource::TYPE_DOCUMENTSDB_DATABASE to the supported resources list, enabling DocumentsDB migrations.


742-885: LGTM! Correct per-database context usage.

The method correctly:

  • Uses dbForProject for metadata operations (index documents, counts)
  • Obtains dbForDatabase via factory for adapter limits and actual index creation

982-1007: LGTM! Robust orphan attribute filtering.

The attribute filtering logic correctly:

  • Checks if the database adapter supports attributes
  • Filters out attributes not in the table schema
  • Preserves system attributes (those starting with $)

The comment clearly explains why this filtering is necessary.

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

122-129: LGTM! Clean reader factory pattern.

The createReader() method provides a clean abstraction for creating the appropriate reader based on the source type, correctly passing the per-database factory to DatabaseReader.


700-717: LGTM! Correct database type normalization and resource creation.

The method correctly:

  • Normalizes legacy database types to TYPE_DATABASE
  • Uses the getDatabase() helper with the correct database type
  • Passes all necessary database attributes including the new database field

1768-1776: LGTM! Clean database resource factory.

The getDatabase() helper correctly routes to DocumentsDB or Database resource based on the database type.


1796-1843: LGTM! Correct entity and record factory helpers.

Both getEntity() and getRecord() correctly route to Collection/Document or Table/Row based on the database type. This design enables proper resource type handling for DocumentsDB vs TablesDB databases.

* formatting
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

55-61: Replace unsafe new static() and add subclass override.

Use new self() here to satisfy static analysis. Implement Document::fromArray() to preserve late binding when invoked on Document.

-        return new static(
+        return new self(
             $array['id'],
             Table::fromArray($array['table'] ?? $array['collection']),
             $array['data'],
             $array['permissions'] ?? []
         );

Add in src/Migration/Resources/Database/Document.php:

public static function fromArray(array $array): self
{
    return new self(
        $array['id'],
        Table::fromArray($array['table'] ?? $array['collection']),
        $array['data'],
        $array['permissions'] ?? []
    );
}
♻️ Duplicate comments (9)
src/Migration/Destinations/Appwrite.php (3)

446-450: Use constant instead of 'documents' magic string.

Replace with Resource::TYPE_DOCUMENTSDB_DATABASE.

-        if ($resource->getTable()->getDatabase()->getType() === 'documents') {
+        if ($resource->getTable()->getDatabase()->getType() === Resource::TYPE_DOCUMENTSDB_DATABASE) {

413-416: Validate database DSN before invoking factory.

Ensure the UtopiaDocument has a non-empty 'database' attribute; handle invalid DSNs deterministically.

-        $dbForDatabase = call_user_func($this->getDatabaseDB, $database);
+        $dsn = (string) $database->getAttribute('database', '');
+        if ($dsn === '') {
+            throw new Exception(
+                resourceName: $resource->getName(),
+                resourceGroup: $resource->getGroup(),
+                resourceId: $resource->getId(),
+                message: 'Invalid or missing database DSN'
+            );
+        }
+        $dbForDatabase = \call_user_func($this->getDatabaseDB, $database);

Also applies to: 541-542, 742-743, 977-978


982-1001: Remove unused foreach value to satisfy PHPMD.

Iterate keys only.

-                        foreach ($row as $key => $value) {
+                        foreach (\array_keys($row->getArrayCopy()) as $key) {
                             if (\str_starts_with($key, '$')) {
                                 continue;
                             }
src/Migration/Sources/Appwrite.php (5)

77-85: Fix deprecated parameter order (PHP 8.1+).

Required parameter must not follow optional.

     public function __construct(
         protected string $project,
         protected string $endpoint,
         protected string $key,
         protected string $source = self::SOURCE_API,
-        protected ?UtopiaDatabase $dbForProject = null,
-        callable $getDatabaseDB
+        callable $getDatabaseDB,
+        protected ?UtopiaDatabase $dbForProject = null
     ) {

735-741: PHPDoc references unknown parameter.

Remove @param for non-existent $databases.

-    /**
-     * @param string $databaseName
-     * @param int $batchSize
-     * @param array<Resource> $databases
-     * @throws Exception
-     */
+    /**
+     * @param string $databaseName
+     * @param int $batchSize
+     * @throws Exception
+     */

779-793: Pass database type, not name, to getEntity().

Ensures Collections are emitted for DocumentsDB.

-                    $newTable = self::getEntity($databaseName, [
+                    $newTable = self::getEntity($database->getType(), [

1169-1186: Pass database type, not name, to getRecord().

Ensures Documents are emitted for DocumentsDB.

-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [

1692-1700: PHPDoc references unknown parameter.

Remove @param for non-existent $documentsDBDatabases.

-    /**
-     * Export DocumentsDB databases (collections and documents)
-     * 
-     * @param int $batchSize
-     * @param array $resources
-     * @param array $documentsDBDatabases
-     */
+    /**
+     * Export DocumentsDB databases (collections and documents)
+     * 
+     * @param int $batchSize
+     * @param array $resources
+     */
src/Migration/Resources/Database/Database.php (1)

47-56: Fix unsafe late-static instantiation and default missing 'database'.

  • Replace new static(...) with new self(...) to satisfy CodeQL/PHPStan.
  • Default database to '' when key missing to keep BC and avoid TypeError.
-        return new static(
+        return new self(
             $array['id'],
             $array['name'],
             createdAt: $array['createdAt'] ?? '',
             updatedAt: $array['updatedAt'] ?? '',
             enabled: $array['enabled'] ?? true,
             originalId: $array['originalId'] ?? '',
             type: $array['type'] ?? 'legacy',
-            database: $array['database']
+            database: $array['database'] ?? ''
         );
🧹 Nitpick comments (3)
src/Migration/Resources/Database/Database.php (1)

34-43: Docblock: mark database as optional.

Reflects the defensive default applied above.

- *     database: string
+ *     database?: string
src/Migration/Destinations/Appwrite.php (1)

60-64: Type the database resolver.

Use callable type instead of mixed.

-    protected mixed $getDatabaseDB;
+    protected callable $getDatabaseDB;
src/Migration/Sources/Appwrite.php (1)

69-73: Type the database resolver property.

Prefer callable|null over mixed.

-    protected mixed $getDatabaseDB;
+    protected ?callable $getDatabaseDB;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bc1eb8 and 1a45ca8.

📒 Files selected for processing (8)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
  • src/Migration/Resource.php (3 hunks)
  • src/Migration/Resources/Database/Database.php (3 hunks)
  • src/Migration/Resources/Database/Row.php (1 hunks)
  • src/Migration/Resources/Database/Table.php (1 hunks)
  • src/Migration/Sources/Appwrite.php (17 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (8 hunks)
  • src/Migration/Transfer.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Migration/Resources/Database/Table.php
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-06-28T09:45:36.026Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Row.php:60-60
Timestamp: 2025-06-28T09:45:36.026Z
Learning: In the utopia-php/migration codebase, the `fromArray` method is not used on Row objects, so mismatches between `jsonSerialize()` output keys and `fromArray()` input expectations for Row class are not problematic.

Applied to files:

  • src/Migration/Resources/Database/Row.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (5)
src/Migration/Transfer.php (1)
src/Migration/Resource.php (1)
  • Resource (5-257)
src/Migration/Sources/Appwrite/Reader/Database.php (10)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/Document.php (1)
  • Document (7-13)
src/Migration/Destinations/Appwrite.php (1)
  • __construct (78-101)
src/Migration/Resources/Database/Row.php (1)
  • __construct (16-24)
src/Migration/Resources/Database/Table.php (2)
  • __construct (20-32)
  • getDatabase (91-94)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1768-1776)
  • report (187-220)
src/Migration/Sources/Appwrite/Reader/API.php (5)
  • __construct (19-23)
  • listDatabases (123-126)
  • report (28-118)
  • listTables (131-138)
  • getSupportForAttributes (244-247)
src/Migration/Resource.php (1)
  • Resource (5-257)
src/Migration/Sources/Appwrite/Reader.php (4)
  • listDatabases (29-29)
  • report (21-21)
  • listTables (38-38)
  • getSupportForAttributes (109-109)
src/Migration/Destinations/Appwrite.php (8)
src/Migration/Resources/Database/Database.php (4)
  • __construct (21-32)
  • getDatabase (99-102)
  • getType (94-97)
  • getName (74-77)
src/Migration/Resources/Database/Row.php (4)
  • __construct (16-24)
  • getTable (86-89)
  • Row (8-98)
  • getName (76-79)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1768-1776)
  • getName (111-114)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (478-481)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
src/Migration/Resources/Database/Table.php (2)
  • getDatabase (91-94)
  • Table (8-110)
src/Migration/Resources/Database/Column.php (3)
  • Column (8-155)
  • getTable (97-100)
  • getType (85-85)
src/Migration/Resources/Database/Database.php (4)
src/Migration/Resources/Database/Row.php (1)
  • fromArray (53-61)
src/Migration/Resources/Database/Table.php (2)
  • fromArray (50-62)
  • getDatabase (91-94)
src/Migration/Resources/Database/Index.php (1)
  • fromArray (76-89)
src/Migration/Sources/Appwrite.php (1)
  • getDatabase (1768-1776)
src/Migration/Sources/Appwrite.php (11)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-501)
  • report (49-152)
  • queryLimit (473-476)
  • queryEqual (418-421)
  • queryCursorAfter (429-471)
  • listDatabases (154-157)
  • getDatabase (40-47)
  • listTables (159-198)
  • listColumns (200-260)
  • listIndexes (262-307)
  • getSupportForAttributes (478-481)
  • querySelect (408-411)
  • listRows (309-360)
  • getRow (362-402)
src/Migration/Resources/Database/Document.php (2)
  • Document (7-13)
  • getName (9-12)
src/Migration/Resource.php (4)
  • Resource (5-257)
  • getName (129-129)
  • isSupported (133-146)
  • getId (148-151)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/DocumentsDB.php (1)
  • DocumentsDB (7-13)
src/Migration/Sources/Appwrite/Reader.php (12)
  • report (21-21)
  • queryLimit (108-108)
  • queryEqual (92-92)
  • queryCursorAfter (100-100)
  • listDatabases (29-29)
  • listTables (38-38)
  • listColumns (47-47)
  • listIndexes (56-56)
  • getSupportForAttributes (109-109)
  • querySelect (83-83)
  • listRows (65-65)
  • getRow (75-75)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Cache.php (1)
  • get (188-195)
src/Migration/Resources/Database/Table.php (5)
  • getDatabase (91-94)
  • Table (8-110)
  • getTableName (96-99)
  • getRowSecurity (101-104)
  • fromArray (50-62)
src/Migration/Resources/Database/Row.php (3)
  • getTable (86-89)
  • fromArray (53-61)
  • Row (8-98)
🪛 GitHub Actions: CodeQL
src/Migration/Resources/Database/Row.php

[error] 55-55: PHPStan: Unsafe usage of new static().

src/Migration/Resources/Database/Database.php

[error] 47-47: PHPStan: Unsafe usage of new static().

src/Migration/Sources/Appwrite.php

[error] 83-83: PHPStan: Deprecated in PHP 8.1: Required parameter $getDatabaseDB follows optional parameter $dbForProject.


[error] 740-740: PHPStan: PHPDoc tag @param references unknown parameter: $databases


[error] 1699-1699: PHPStan: PHPDoc tag @param references unknown parameter: $documentsDBDatabases

🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (2)
src/Migration/Resource.php (1)

33-38: Additions look consistent and useful.

New resource types and DATABASE_TYPE_RESOURCE_MAP are coherent with downstream usage.

Also applies to: 74-80, 97-109

src/Migration/Transfer.php (1)

37-46: LGTM.

Databases group now includes DocumentsDB entities/records. No issues.

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

♻️ Duplicate comments (9)
src/Migration/Destinations/Appwrite.php (3)

984-984: Remove unused loop variable.

Static analysis correctly identifies that $value is unused in the foreach loop at line 984. Consider using only the key or a throwaway variable name.

Apply this diff:

-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {

Or iterate only over keys:

-                        foreach ($row as $key => $value) {
+                        foreach (array_keys($row->getArrayCopy()) as $key) {

447-450: Use constant instead of hardcoded 'documents' string.

The check $resource->getTable()->getDatabase()->getType() === 'documents' uses a magic string. This should use the appropriate constant for consistency and maintainability.

Apply this diff:

-    if ($resource->getTable()->getDatabase()->getType() === 'documents') {
+    if ($resource->getTable()->getDatabase()->getType() === Resource::TYPE_DOCUMENTSDB_DATABASE) {
         $resource->setStatus(Resource::STATUS_SKIPPED, 'Columns not supported for documents database');
         return false;
     }

413-413: Validate database identifier before invoking factory.

At multiple locations (lines 413, 541, 742, 977), $database->getAttribute('database') is passed directly to the factory callable without validation. If the attribute is null or empty, the factory may fail unexpectedly.

Consider adding validation before each factory invocation. Example for line 413:

+    $databaseDSN = $database->getAttribute('database');
+    if (!\is_string($databaseDSN) || $databaseDSN === '') {
+        throw new Exception(
+            resourceName: $resource->getName(),
+            resourceGroup: $resource->getGroup(),
+            resourceId: $resource->getId(),
+            message: 'Database DSN is required',
+        );
+    }
-    $dbForDatabase = call_user_func($this->getDatabasesDB, $database);
+    $dbForDatabase = call_user_func($this->getDatabasesDB, $database);

Apply similar guards at lines 541, 742, and 977.

Also applies to: 541-541, 742-742, 977-977

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

77-109: Fix deprecated parameter order in PHP 8.1.

Required parameter $getDatabasesDB follows optional parameter $dbForProject, which is deprecated in PHP 8.1 and will cause a fatal error in PHP 9.0.

Apply this diff to reorder parameters:

 public function __construct(
     protected string $project,
     protected string $endpoint,
     protected string $key,
     protected string $source = self::SOURCE_API,
-    protected ?UtopiaDatabase $dbForProject = null,
-    callable $getDatabasesDB
+    callable $getDatabasesDB,
+    protected ?UtopiaDatabase $dbForProject = null
 ) {

Important: Ensure all call sites are updated to match the new parameter order.


779-795: Pass database type instead of name to getEntity.

At line 779, getEntity is called with $databaseName (the cache key/resource name like "Database" or "DocumentsDB"), but getEntity expects the database type (e.g., Resource::TYPE_DATABASE or Resource::TYPE_DOCUMENTSDB_DATABASE) to determine whether to return a Table or Collection resource.

Apply this diff:

-                $newTable = self::getEntity($databaseName, [
+                $newTable = self::getEntity($database->getType(), [

This ensures DocumentsDB tables are correctly mapped to Collection resources and regular tables to Table resources.


1170-1186: Pass database type instead of name to getRecord.

At line 1170, getRecord is called with $table->getDatabase()->getDatabaseName(), but getRecord expects the database type to decide between Document and Row resources. Using the name will cause all records to default to Row.

Apply this diff:

-                $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                $row = self::getRecord($table->getDatabase()->getType(), [

This ensures DocumentsDB rows become Document resources as intended, and regular rows remain Row resources.


734-741: Fix PHPDoc: remove unknown parameter.

The PHPDoc references parameter $databases, but the method signature for exportEntities has no such parameter.

Apply this diff:

 /**
  * @param string $databaseName
  * @param int $batchSize
- * @param array<Resource> $databases
  * @throws Exception
  */

1692-1699: Fix PHPDoc: remove unknown parameter.

The PHPDoc references parameter $documentsDBDatabases, but the method signature for exportDocumentsDB has no such parameter.

Apply this diff:

 /**
  * Export DocumentsDB databases (collections and documents)
  * 
  * @param int $batchSize
  * @param array $resources
- * @param array $documentsDBDatabases
  */
src/Migration/Sources/Appwrite/Reader/Database.php (1)

40-47: Guard per-database resolver against empty or null DSN.

The getDatabase helper does not validate that $databaseDSN is a non-empty string before invoking the factory callable. If $databaseDSN is an empty string, the factory may fail unexpectedly or return an incorrect database instance.

Apply this diff to add validation:

 private function getDatabase(?string $databaseDSN = null): UtopiaDatabase
 {
-    if ($this->getDatabasesDB !== null && $databaseDSN !== null) {
+    if ($this->getDatabasesDB !== null && \is_string($databaseDSN) && $databaseDSN !== '') {
         return call_user_func($this->getDatabasesDB, new UtopiaDocument(['database' => $databaseDSN]));
     }

     return $this->dbForProject;
 }

This ensures the factory is only called with valid, non-empty DSNs, and falls back to dbForProject when the DSN is null or empty.

🧹 Nitpick comments (1)
src/Migration/Sources/Appwrite/Reader/Database.php (1)

25-35: Type the callable property for clarity.

The $getDatabasesDB property is declared as mixed with a docblock. Consider using a more specific type hint for better type safety.

Apply this diff:

-    private mixed $getDatabasesDB;
+    private ?callable $getDatabasesDB;

This clarifies that the property holds either a callable or null, improving type safety and IDE support.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a45ca8 and e8faed3.

📒 Files selected for processing (3)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
  • src/Migration/Sources/Appwrite.php (17 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (8 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Sources/Appwrite.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Sources/Appwrite/Reader/Database.php
🧬 Code graph analysis (3)
src/Migration/Sources/Appwrite.php (6)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-501)
  • report (49-152)
  • queryLimit (473-476)
  • queryEqual (418-421)
  • queryCursorAfter (429-471)
  • listDatabases (154-157)
  • getDatabase (40-47)
  • listTables (159-198)
  • listColumns (200-260)
  • listIndexes (262-307)
  • getSupportForAttributes (478-481)
  • querySelect (408-411)
  • listRows (309-360)
  • getRow (362-402)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Resources/Database/Document.php (2)
  • Document (7-13)
  • getName (9-12)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/DocumentsDB.php (1)
  • DocumentsDB (7-13)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Destinations/Appwrite.php (6)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1768-1776)
  • getName (111-114)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (478-481)
src/Migration/Resources/Database/Database.php (4)
  • __construct (21-32)
  • getDatabase (99-102)
  • getType (94-97)
  • getName (74-77)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
src/Migration/Sources/Appwrite/Reader.php (1)
  • getSupportForAttributes (109-109)
src/Migration/Sources/Appwrite/Reader/Database.php (12)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (8-15)
src/Migration/Resources/Database/Column.php (2)
  • Column (8-155)
  • __construct (40-55)
src/Migration/Resources/Database/Document.php (1)
  • Document (7-13)
src/Migration/Destinations/Appwrite.php (1)
  • __construct (78-101)
src/Migration/Sources/Appwrite.php (3)
  • __construct (77-109)
  • getDatabase (1768-1776)
  • report (187-220)
src/Migration/Resources/Database/Table.php (2)
  • __construct (20-32)
  • getDatabase (91-94)
src/Migration/Resources/Database/Row.php (1)
  • __construct (16-24)
src/Migration/Sources/Appwrite/Reader/API.php (5)
  • __construct (19-23)
  • listDatabases (123-126)
  • report (28-118)
  • listTables (131-138)
  • getSupportForAttributes (244-247)
src/Migration/Transfer.php (1)
  • __construct (107-116)
src/Migration/Resources/Database/Index.php (1)
  • __construct (29-41)
src/Migration/Resource.php (3)
  • Resource (5-257)
  • isSupported (133-146)
  • getId (148-151)
🪛 GitHub Actions: CodeQL
src/Migration/Sources/Appwrite.php

[error] 83-83: Deprecated in PHP 8.1: Required parameter $getDatabasesDB follows optional parameter $dbForProject.


[error] 740-740: PHPDoc tag @param references unknown parameter: $databases


[error] 1699-1699: PHPDoc tag @param references unknown parameter: $documentsDBDatabases

🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (9)
src/Migration/Sources/Appwrite/Reader/Database.php (3)

76-149: Per-database reporting logic is well-structured.

The report method correctly normalizes legacy database types and routes resource counting to the appropriate database instances using DATABASE_TYPE_RESOURCE_MAP. The per-database context propagation is properly implemented.


478-481: New public method getSupportForAttributes is correctly implemented.

The method delegates to the adapter and provides a clean interface for checking attribute support.


341-359: Per-database routing verified
Database::fromArray() requires the database key (no default in constructor), so getDatabase() always returns the configured DSN. listRows correctly routes queries per database.

src/Migration/Destinations/Appwrite.php (2)

78-101: Constructor correctly accepts per-database factory.

The constructor properly stores the $getDatabasesDB callable and validates its presence for database sources.


382-437: Per-database entity creation is correctly implemented.

The createEntity method properly retrieves the per-database instance using the factory and routes table creation to the correct database context.

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

122-129: Reader factory method is well-implemented.

The createReader method correctly instantiates the appropriate reader based on the source type.


667-732: Database export logic correctly uses static helper.

The exportDatabases method properly normalizes database types and constructs Database or DocumentsDB resources using the getDatabase helper.


1768-1843: Static mapping helpers are well-structured.

The getDatabase, getEntity, and getRecord helpers correctly map database types to the appropriate resource classes (Database/DocumentsDB, Table/Collection, Row/Document). Good use of switch statements for clarity.


1617-1754: Per-database export methods are correctly implemented.

The exportTablesDB and exportDocumentsDB methods properly separate export flows for Tables/Columns/Rows vs Collections/Documents, routing to the appropriate entity types.

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

♻️ Duplicate comments (3)
src/Migration/Destinations/Appwrite.php (1)

413-416: Validate database identifier before creating per‑DB adapter

Ensure the database doc has a valid 'database' attribute before calling the factory; fail fast with a clear error.

-        $dbForDatabase = call_user_func($this->getDatabasesDB, $database);
+        $dsn = $database->getAttribute('database');
+        if (!\is_string($dsn) || $dsn === '') {
+            throw new Exception(
+                resourceName: $resource->getName(),
+                resourceGroup: $resource->getGroup(),
+                resourceId: $resource->getId(),
+                message: 'Invalid or missing database identifier (DSN)'
+            );
+        }
+        $dbForDatabase = call_user_func($this->getDatabasesDB, $database);
src/Migration/Resources/Database/Database.php (1)

55-56: Handle missing 'database' key to preserve BC

Use a fallback to avoid TypeError when callers omit 'database'.

Apply:

-            database: $array['database']
+            database: $array['database'] ?? ''
src/Migration/Sources/Appwrite.php (1)

1167-1183: Wrong discriminator passed to getRecord; Documents become Rows

Pass the database type, not the name, so documents route to Document::fromArray().

-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [
🧹 Nitpick comments (2)
src/Migration/Resources/Database/Collection.php (1)

30-41: Defensive default for rowSecurity

If both rowSecurity and documentSecurity are absent, null is passed to a bool parameter. Default to false.

Apply:

-            rowSecurity: $array['rowSecurity'] ?? $array['documentSecurity'],
+            rowSecurity: $array['rowSecurity'] ?? ($array['documentSecurity'] ?? false),
src/Migration/Destinations/Appwrite.php (1)

982-999: Remove unused loop variable

$value is unused; avoid noise and PHPMD warning.

-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8faed3 and b77e8c2.

📒 Files selected for processing (8)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
  • src/Migration/Resource.php (3 hunks)
  • src/Migration/Resources/Database/Collection.php (1 hunks)
  • src/Migration/Resources/Database/Database.php (4 hunks)
  • src/Migration/Resources/Database/Document.php (1 hunks)
  • src/Migration/Resources/Database/DocumentsDB.php (1 hunks)
  • src/Migration/Sources/Appwrite.php (17 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Migration/Resources/Database/Document.php
  • src/Migration/Resources/Database/DocumentsDB.php
  • src/Migration/Resource.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (4)
src/Migration/Resources/Database/Collection.php (5)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • getName (74-77)
  • fromArray (45-57)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
src/Migration/Resources/Database/Table.php (3)
  • Table (8-110)
  • getName (81-84)
  • fromArray (50-62)
src/Migration/Resources/Database/Document.php (2)
  • getName (9-12)
  • fromArray (41-50)
src/Migration/Resources/Database/DocumentsDB.php (3)
  • getName (9-12)
  • fromArray (25-37)
  • DocumentsDB (7-38)
src/Migration/Destinations/Appwrite.php (8)
src/Migration/Resources/Database/Database.php (3)
  • __construct (21-32)
  • getDatabase (99-102)
  • getName (74-77)
src/Migration/Sources/Appwrite.php (3)
  • __construct (76-108)
  • getDatabase (1764-1772)
  • getName (110-113)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (478-481)
src/Migration/Resources/Database/Row.php (4)
  • __construct (16-24)
  • getTable (86-89)
  • Row (8-98)
  • getName (76-79)
src/Migration/Resources/Database/Table.php (3)
  • __construct (20-32)
  • getDatabase (91-94)
  • Table (8-110)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
src/Migration/Resources/Database/Column.php (2)
  • Column (8-155)
  • getTable (97-100)
src/Migration/Sources/Appwrite.php (10)
src/Migration/Resources/Database/Database.php (6)
  • Database (19-103)
  • getName (74-77)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-501)
  • report (49-152)
  • queryLimit (473-476)
  • queryEqual (418-421)
  • queryCursorAfter (429-471)
  • listDatabases (154-157)
  • getDatabase (40-47)
  • listTables (159-198)
  • listColumns (200-260)
  • listIndexes (262-307)
  • getSupportForAttributes (478-481)
  • querySelect (408-411)
  • listRows (309-360)
  • getRow (362-402)
src/Migration/Resources/Database/Collection.php (3)
  • Collection (7-43)
  • getName (9-12)
  • fromArray (30-42)
src/Migration/Resources/Database/Document.php (3)
  • Document (7-51)
  • getName (9-12)
  • fromArray (41-50)
src/Migration/Resources/Database/DocumentsDB.php (2)
  • DocumentsDB (7-38)
  • fromArray (25-37)
src/Migration/Resources/Database/Row.php (4)
  • getName (76-79)
  • getTable (86-89)
  • fromArray (53-61)
  • Row (8-98)
src/Migration/Resources/Database/Table.php (6)
  • getName (81-84)
  • getDatabase (91-94)
  • Table (8-110)
  • getTableName (96-99)
  • getRowSecurity (101-104)
  • fromArray (50-62)
src/Migration/Sources/Appwrite/Reader.php (12)
  • report (21-21)
  • queryLimit (108-108)
  • queryEqual (92-92)
  • queryCursorAfter (100-100)
  • listDatabases (29-29)
  • listTables (38-38)
  • listColumns (47-47)
  • listIndexes (56-56)
  • getSupportForAttributes (109-109)
  • querySelect (83-83)
  • listRows (65-65)
  • getRow (75-75)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Cache.php (1)
  • get (188-195)
src/Migration/Resources/Database/Database.php (3)
src/Migration/Sources/Appwrite.php (1)
  • getDatabase (1764-1772)
src/Migration/Sources/Appwrite/Reader/Database.php (1)
  • getDatabase (40-47)
src/Migration/Resources/Database/Table.php (1)
  • getDatabase (91-94)
🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (6)
src/Migration/Resources/Database/Collection.php (1)

9-12: Resource name OK

Returns the correct type for Collection.

src/Migration/Resources/Database/Database.php (1)

98-102: Getter addition LGTM

getDatabase() accessor is straightforward and consistent.

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

121-122: Support matrix updated

Including TYPE_DOCUMENTSDB_DATABASE here is correct.

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

76-83: Constructor parameter order fixed

Required callable precedes optional args; resolves PHP 8.1 deprecation.


631-657: Conditional export split LGTM

Branching to export relational vs documents dbs is correct and aligns with cache usage.


697-705: Legacy constants verified Resource::TYPE_DATABASE_LEGACY and Resource::TYPE_DATABASE_TABLESDB are declared in src/Migration/Resource.php; no action needed.

Comment on lines 700 to 713
if ($databaseType === Resource::TYPE_DATABASE_LEGACY || $databaseType === Resource::TYPE_DATABASE_TABLESDB) {
$databaseType = Resource::TYPE_DATABASE;
}

$databases[] = $newDatabase;
if (Resource::isSupported($databaseType, $resources)) {
$newDatabase = self::getDatabase($databaseType, [
'id' => $database['$id'],
'name' => $database['name'],
'createdAt' => $database['$createdAt'],
'updatedAt' => $database['$updatedAt'],
'type' => $databaseType,
'database' => $database['database']
]);
$databases[] = $newDatabase;
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

DSN may be absent in API mode; add safe default

When using APIReader, database['database'] might be missing. Default to empty string to avoid fromArray crash.

-                        'database' => $database['database']
+                        'database' => $database['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
if ($databaseType === Resource::TYPE_DATABASE_LEGACY || $databaseType === Resource::TYPE_DATABASE_TABLESDB) {
$databaseType = Resource::TYPE_DATABASE;
}
$databases[] = $newDatabase;
if (Resource::isSupported($databaseType, $resources)) {
$newDatabase = self::getDatabase($databaseType, [
'id' => $database['$id'],
'name' => $database['name'],
'createdAt' => $database['$createdAt'],
'updatedAt' => $database['$updatedAt'],
'type' => $databaseType,
'database' => $database['database']
]);
$databases[] = $newDatabase;
if ($databaseType === Resource::TYPE_DATABASE_LEGACY || $databaseType === Resource::TYPE_DATABASE_TABLESDB) {
$databaseType = Resource::TYPE_DATABASE;
}
if (Resource::isSupported($databaseType, $resources)) {
$newDatabase = self::getDatabase($databaseType, [
'id' => $database['$id'],
'name' => $database['name'],
'createdAt' => $database['$createdAt'],
'updatedAt' => $database['$updatedAt'],
'type' => $databaseType,
'database' => $database['database'] ?? ''
]);
$databases[] = $newDatabase;
🤖 Prompt for AI Agents
In src/Migration/Sources/Appwrite.php around lines 700 to 713, the code assumes
$database['database'] exists and will crash when absent (APIReader mode); change
the array entry to supply a safe default by using the null coalescing operator
(or equivalent) so 'database' => $database['database'] ?? '' is passed to
getDatabase, ensuring fromArray won't fail when the DSN/key is missing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Migration/Destinations/Appwrite.php (2)

982-1001: Remove unused loop variable.

The $value variable is declared but never used in the loop. This is flagged by static analysis.

Apply this diff:

-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {
                             if (\str_starts_with($key, '$')) {
                                 continue;
                             }

Based on learnings


446-449: DocumentsDB detection will never trigger; columns won't be skipped.

The check uses getName() which returns the resource type (TYPE_DATABASE or TYPE_DOCUMENTSDB_DATABASE) based on the polymorphic class type. However, when Table resources are deserialized via Table::fromArray(), the database reference is always instantiated as a Database object (not DocumentsDB), so getName() will return TYPE_DATABASE even for DocumentsDB databases.

Use getType() instead, which checks the database's type property:

-        if ($resource->getTable()->getDatabase()->getName() === Resource::TYPE_DOCUMENTSDB_DATABASE) {
+        if ($resource->getTable()->getDatabase()->getType() === 'documents') {
             $resource->setStatus(Resource::STATUS_SKIPPED, 'Columns not supported for DocumentsDB');
             return false;
         }

Note: If a constant exists for the database type 'documents', use that instead of the magic string for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b77e8c2 and 0a3abfa.

📒 Files selected for processing (1)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (1)
src/Migration/Destinations/Appwrite.php (7)
src/Migration/Resources/Database/Row.php (4)
  • __construct (16-24)
  • getTable (86-89)
  • getName (76-79)
  • Row (8-98)
src/Migration/Resources/Database/Table.php (3)
  • __construct (20-32)
  • getDatabase (91-94)
  • Table (8-110)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (478-481)
src/Migration/Sources/Appwrite.php (3)
  • __construct (76-108)
  • getDatabase (1764-1772)
  • getName (110-113)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resources/Database/Database.php (3)
  • __construct (21-32)
  • getDatabase (99-102)
  • getName (74-77)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (1)
src/Migration/Destinations/Appwrite.php (1)

60-100: LGTM! Constructor properly accepts per-database context support.

The addition of the $getDatabasesDB callable and $dbForProject parameter enables proper per-database context switching throughout the destination implementation.

@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as draft October 14, 2025 19:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

75-109: Fix PHPDoc parameter name mismatch.

The PHPDoc at line 81 references $getDatabaseDSN, but the actual parameter at line 90 is named $getDatabasesDSN (note the extra 's'). This mismatch is causing the PHPStan pipeline failure.

Apply this diff to fix the PHPDoc:

-     * @param callable(string $databaseType):string $getDatabaseDSN
+     * @param callable(string $databaseType):string $getDatabasesDSN
♻️ Duplicate comments (1)
src/Migration/Destinations/Appwrite.php (1)

990-1011: Remove unused loop variable.

Static analysis correctly identifies that $value at line 992 is unused. The loop only requires the key to check and remove orphan attributes.

Apply this diff to remove the unused variable:

-                    foreach ($this->rowBuffer as $row) {
-                        foreach ($row as $key => $value) {
+                    foreach ($this->rowBuffer as $row) {
+                        foreach ($row as $key => $_) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a3abfa and de06db2.

📒 Files selected for processing (1)
  • src/Migration/Destinations/Appwrite.php (38 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
PR: utopia-php/migration#80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Destinations/Appwrite.php
🧬 Code graph analysis (1)
src/Migration/Destinations/Appwrite.php (7)
src/Migration/Resources/Database/Database.php (4)
  • __construct (21-32)
  • getType (94-97)
  • getDatabase (99-102)
  • getName (74-77)
src/Migration/Resources/Database/Row.php (4)
  • __construct (16-24)
  • getTable (86-89)
  • getName (76-79)
  • Row (8-98)
src/Migration/Resources/Database/Table.php (3)
  • __construct (20-32)
  • Table (8-110)
  • getDatabase (91-94)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (478-481)
src/Migration/Sources/Appwrite.php (3)
  • __construct (76-108)
  • getDatabase (1764-1772)
  • getName (110-113)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (188-195)
src/Migration/Resource.php (2)
  • Resource (5-257)
  • getName (129-129)
🪛 GitHub Actions: CodeQL
src/Migration/Destinations/Appwrite.php

[error] 84-84: PHPStan: PHPDoc tag @param references unknown parameter: $getDatabaseDSN.

🪛 PHPMD (2.15.0)
src/Migration/Destinations/Appwrite.php

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

(UnusedLocalVariable)

🔇 Additional comments (5)
src/Migration/Destinations/Appwrite.php (5)

119-149: LGTM!

The addition of Resource::TYPE_DOCUMENTSDB_DATABASE to the supported resources list correctly reflects the new DocumentsDB support introduced in this PR.


234-327: LGTM!

The import flow correctly handles the per-project context ($this->dbForProject) and routes Resource::TYPE_DOCUMENTSDB_DATABASE to the appropriate handler.


334-383: LGTM!

The createDatabase method correctly uses the project-scoped context ($this->dbForProject) and invokes the getDatabaseDSN callable to populate the database field, enabling cross-location source/destination mapping.


723-927: LGTM!

The createIndex method correctly separates concerns: it uses $this->dbForProject for project-scoped document operations and $dbForDatabase (obtained via the callable) for database-scoped index creation and adapter-specific limits.


391-446: <!-- [scratchpad]
[task review_file_1/1 inspect_getCallbacks/2]
Inspect getDatabaseDSN usage.
[observations]

  • Found assignment at lines 89-91.
  • No calls to getDatabaseDSN in Appwrite.php.
    [pending]
  • Determine intended use of getDatabaseDSN callback.
    [analysis]
  • The class receives getDatabaseDSN but never uses it in createEntity.
  • The getDatabasesDB callback likely includes DSN resolution.
  • Cannot confirm that getDatabasesDB validates the DSN attribute.
    [actions]
  • Recommend verifying that the getDatabasesDB implementation handles missing DSN.
    [done]

Verify DSN handling in getDatabasesDB callback.
Before calling call_user_func($this->getDatabasesDB, $database), ensure the injected callback either:

  1. Validates $database->getAttribute('database') (or database type) and throws a clear exception on missing/invalid DSN, or
  2. Leverages $this->getDatabaseDSN to resolve and validate the DSN.

If the callback does not include these checks, add a pre-flight validation:

$databaseType = $database->getAttribute('database');
if (empty($databaseType)) {
    throw new Exception(
        resourceName: $resource->getName(),
        resourceGroup: $resource->getGroup(),
        resourceId: $resource->getId(),
        message: 'Database type attribute is missing or invalid',
    );
}
$dsn = call_user_func($this->getDatabaseDSN, $databaseType);

Then pass the full document to getDatabasesDB.

@ArnabChatterjee20k ArnabChatterjee20k changed the title documents db migration documents db + vector db migration Nov 9, 2025
@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as ready for review November 19, 2025 16:19
@ArnabChatterjee20k ArnabChatterjee20k marked this pull request as draft November 19, 2025 16:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

637-712: Arrow function parameter mismatch in handleExportEntityScopedResources causes runtime errors

$handleExportEntityScopedResources always calls the callback with two arguments: ($entityKey, $entityResource), but the callbacks for field and index are declared with only one parameter. In modern PHP this results in an ArgumentCountError at runtime.

  • field: fn ($entityKey) => $this->exportFields($entityKey, $batchSize)
  • index: fn ($entityKey) => $this->exportIndexes($entityKey, $batchSize)

Called as: $callback($entityKey, $entityResource).

Update those arrow functions to accept (and ignore) the second parameter.

-        // field
-        if (!$handleExportEntityScopedResources('field', fn ($entityKey) => $this->exportFields($entityKey, $batchSize))) {
+        // field
+        if (!$handleExportEntityScopedResources(
+            'field',
+            fn (string $entityKey, array $_) => $this->exportFields($entityKey, $batchSize)
+        )) {
             return;
         }
 
-        // index
-        if (!$handleExportEntityScopedResources('index', fn ($entityKey) => $this->exportIndexes($entityKey, $batchSize))) {
+        // index
+        if (!$handleExportEntityScopedResources(
+            'index',
+            fn (string $entityKey, array $_) => $this->exportIndexes($entityKey, $batchSize)
+        )) {
             return;
         }
 
         // record
-        if (!$handleExportEntityScopedResources('record', fn ($entityKey, $entityResource) => $this->exportRecords($entityKey, $entityResource['field'], $batchSize))) {
+        if (
+            !$handleExportEntityScopedResources(
+                'record',
+                fn (string $entityKey, array $entityResource) => $this->exportRecords($entityKey, $entityResource['field'], $batchSize)
+            )
+        ) {
             return;
         }

79-125: Critical: Appwrite constructor calls have incorrect argument order and types in both README and MigrationCLI

Verification reveals the new callable $getDatabasesDB wiring is broken:

  • README.md:32 – Only passes 3 arguments; missing the $getDatabasesDB callable entirely.
  • bin/MigrationCLI.php:213–219 – Arguments in wrong order and types:
    • arg 4 is Appwrite::SOURCE_DATABASE (string constant), but constructor expects callable $getDatabasesDB
    • arg 5 is $this->getDatabase('source') (database object), but constructor expects string $source

Both call sites must be corrected to match the new constructor signature: __construct(string $project, string $endpoint, string $key, callable $getDatabasesDB, string $source = self::SOURCE_API, ?UtopiaDatabase $dbForProject = null, array $queries = []).

♻️ Duplicate comments (3)
src/Migration/Sources/Appwrite/Reader/Database.php (1)

25-47: Improve type safety and guard against empty database DSN.

Two improvements needed for the per-database resolver:

  1. Type the callable explicitly instead of mixed for better type safety and IDE support.
  2. Guard against empty DSN string to prevent factory invocation with invalid identifiers.

Apply this diff:

     /**
-     * @var callable(UtopiaDocument|null): UtopiaDatabase
+     * @var ?callable(UtopiaDocument): UtopiaDatabase
     */
-    private mixed $getDatabasesDB;
+    private $getDatabasesDB;

     public function __construct(
         private readonly UtopiaDatabase $dbForProject,
         ?callable $getDatabasesDB = null
     ) {
         $this->getDatabasesDB = $getDatabasesDB;
     }

     /**
      * Get the appropriate database instance for the given database DSN
      */
     private function getDatabase(?string $databaseDSN = null): UtopiaDatabase
     {
-        if ($this->getDatabasesDB !== null && $databaseDSN !== null) {
-            return ($this->getDatabasesDB)(new UtopiaDocument(['database' => $databaseDSN]));
+        if ($this->getDatabasesDB !== null && !empty($databaseDSN)) {
+            return ($this->getDatabasesDB)(new UtopiaDocument(['database' => $databaseDSN]));
         }

         return $this->dbForProject;
     }

This ensures the factory is only called with valid, non-empty DSN values and improves type clarity.

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

721-784: Default DSN for API mode to avoid undefined index / type error in exportDatabases

In exportDatabases(), the payload for getDatabase() unconditionally accesses $database['database']:

'database' => $database['database']

When using the API reader (APIReader::listDatabases()), the Appwrite API response may not include a database/DSN field. In that case this access will:

  • Trigger an undefined index notice.
  • Pass null into Database::fromArray(), which expects a non‑nullable string $database and will cause a TypeError.

Using a safe default (empty string) avoids crashes when DSN is not available, which matches the previous review guidance for this path.

-                        'database' => $database['database']
+                        'database' => $database['database'] ?? ''

Also applies to: 751-767


791-858: Database type vs name confusion breaks DocumentsDB/VectorDB entity & record mapping

There are a couple of intertwined issues in exportEntities() and exportRecords() that affect DocumentsDB/VectorDB:

  1. Wrong discriminator passed into getEntity()

    $newTable = self::getEntity($databaseName, [ ... ]);

    Here $databaseName is actually the cache key ('database' | 'documentsdb' | 'vectordb'), not necessarily the runtime type of $database. While it currently happens to match, it’s safer and clearer to pass the actual type from the Database resource.

  2. Using getDatabaseName() as the discriminator for getRecord()

    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [ ... ]);

    getRecord() switches on the database type:

    switch ($databaseType) {
        case Resource::TYPE_DATABASE_DOCUMENTSDB:
        case Resource::TYPE_DATABASE_VECTORDB:
            return Document::fromArray($record);
        default:
            return Row::fromArray($record);
    }

    Passing getDatabaseName() here couples behaviour to whatever was placed in the name field of the nested database array in exportEntities(). With the current code, that field holds the cache key / type string rather than the human database name, and any future change (e.g. making it the real name as requested in previous reviews) will silently downgrade DocumentsDB/VectorDB records back to Row.

    To make this robust and match the intent of DocumentsDB/VectorDB:

    • Use the database resource type ($database->getType()) as the discriminator for both entities and records.
    • Store the real database name in the nested database['name'] field.

    Concretely:

-    private function exportEntities(string $databaseName, int $batchSize): void
+    private function exportEntities(string $databaseName, int $batchSize): void
     {
         $databases = $this->cache->get($databaseName);
         foreach ($databases as $database) {
             /** @var Database $database */
@@
-                $response = $this->reader->listTables($database, $queries);
+                $response = $this->reader->listTables($database, $queries);
                 foreach ($response as $table) {
-                    $newTable = self::getEntity($databaseName, [
+                    $newTable = self::getEntity($database->getType(), [
                         'id' => $table['$id'],
                         'name' => $table['name'],
                         'documentSecurity' => $table['documentSecurity'],
                         'permissions' => $table['$permissions'],
                         'createdAt' => $table['$createdAt'],
                         'updatedAt' => $table['$updatedAt'],
                         'database' => [
                             'id' => $database->getId(),
-                            'name' => $databaseName,
-                            'type' => $database->getType(),
-                            'database' => $database->getDatabase(),
+                            // Human-readable database name
+                            'name' => $database->getDatabaseName(),
+                            // Discriminator for DocumentsDB / VectorDB behaviour
+                            'type' => $database->getType(),
+                            // DSN / underlying database identifier
+                            'database' => $database->getDatabase(),
                         ]
                     ]);
-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $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(),
+                                'name' => $table->getDatabase()->getDatabaseName(),
                                 'type' => $table->getDatabase()->getType(),
                                 'database' => $table->getDatabase()->getDatabase(),
                             ]
                         ],
                         'data' => $row,
                         'permissions' => $permissions
                     ]);

This aligns entity/record construction with Resource::DATABASE_TYPE_RESOURCE_MAP and ensures DocumentsDB/VectorDB rows become Document resources as intended, independent of how the human‑readable database name is set.

Also applies to: 825-840, 1052-1068

🧹 Nitpick comments (14)
src/Migration/Resources/Database/VectorDB.php (1)

14-36: Align fromArray phpdoc with actual array shape

fromArray() reads $array['type'] but the phpdoc for $array doesn’t declare a type key, while other keys that are treated as optional in code (createdAt, updatedAt, enabled, originalId, database) are documented as mandatory.

Consider updating the docblock to something like:

/**
 * @param array{
 *     id: string,
 *     name: string,
 *     type?: string,
 *     createdAt?: string,
 *     updatedAt?: string,
 *     enabled?: bool,
 *     originalId?: string|null,
 *     database?: string
 * } $array
 */

This keeps static analysis and IDE hints in sync with the actual usage.

src/Migration/Resources/Database/Attribute/Email.php (1)

8-36: Consider adding fromArray for Email/IP to match other attribute subclasses

Email (and IP) currently only customize the constructor and getType(), while many other attribute classes (e.g. Integer, Point, Polygon, ObjectType, Line, Vector, Decimal) also expose a fromArray() factory that mirrors their JSON shape.

If you expect to deserialize email/IP attributes from arrays the same way, it may be worth adding:

public static function fromArray(array $array): self
{
    return new self(
        $array['key'],
        Collection::fromArray($array['table'] ?? $array['collection']),
        required: $array['required'],
        default: $array['default'] ?? null,
        array: $array['array'],
        size: $array['size'] ?? 254,
        createdAt: $array['createdAt'] ?? '',
        updatedAt: $array['updatedAt'] ?? '',
    );
}

(and the analogous method for IP) to keep the attribute API consistent.

src/Migration/Resources/Database/Attribute/Integer.php (1)

10-106: Revisit Integer min/max defaults, size inference, and accessors

The implementation works, but a few details could be sharpened:

  • Unsigned vs signed defaults: When $min/$max are omitted, you always fall back to PHP_INT_MIN/PHP_INT_MAX, regardless of $signed. For signed === false it’s more intuitive for the default range to start at 0. Example:
if ($min === null) {
    $min = $signed ? PHP_INT_MIN : 0;
}
if ($max === null) {
    $max = PHP_INT_MAX; // or a DB-specific upper bound
}
  • size calculation and signed: size is currently 8 whenever $max > 2147483647. This may over‑promote unsigned 32‑bit ranges (0–4,294,967,295) to 8 bytes even though many engines store them in 4 bytes regardless of signedness. If you care about mirroring the storage type, you might want to factor $signed (or explicit DB type info) into this decision instead of a hard threshold.

  • getMin/getMax return types: Because the constructor always fills formatOptions['min']/['max'], getMin()/getMax() never actually return null, despite the ?int return type. You could either:

    • change the signatures to int, or
    • allow null to propagate (skip the defaults) if “no constraint” is a valid state you care about.

None of these are blockers, but tightening them would make the API more predictable for consumers.

src/Migration/Resources/Database/DocumentsDB.php (1)

14-36: Align DocumentsDB::fromArray phpdoc with its actual input shape

The implementation assumes optional type and database keys and falls back to 'legacy', but the phpdoc doesn’t mention type at all and documents database as required. This makes the declared array shape stricter/different than reality and can confuse static analysis or callers.

Consider updating the phpdoc to reflect the real, optional shape, e.g.:

-     *     originalId: string|null,
-     *     database: string
+     *     originalId: string|null,
+     *     type?: string,
+     *     database?: string
src/Migration/Resources/Database/Attribute/Decimal.php (1)

41-88: Make Decimal::fromArray more robust and consistent with Integer

fromArray() currently assumes formatOptions['min'] and ['max'] are always present:

min: $array['formatOptions']['min'],
max: $array['formatOptions']['max'],

Integer’s fromArray() uses ?? null, which is safer if the keys are missing or coming from older payloads. You could mirror that here:

-            min: $array['formatOptions']['min'],
-            max: $array['formatOptions']['max'],
+            min: $array['formatOptions']['min'] ?? null,
+            max: $array['formatOptions']['max'] ?? null,

This keeps the constructor signature unchanged while avoiding notices on partial inputs.

src/Migration/Resources/Database/Attribute/Boolean.php (1)

10-71: Boolean attribute wiring looks good; consider updating nested database doc

Constructor and fromArray() are consistent with the other simple attributes (e.g. DateTime), and the Collection resolution via $array['table'] ?? $array['collection'] matches the new Collection model.

The only minor nit is that the phpdoc for the nested database array omits the type field, even though Collection::fromArray() now relies on database.type. If all callers will provide that, you might want to reflect it in the docblock for static analysis.

src/Migration/Resources/Database/Attribute/Vector.php (1)

10-71: Vector attribute implementation is consistent; docs could mention database.type

The Vector attribute’s constructor and fromArray() mirror the existing Columns\Vector and other attribute implementations, and correctly use Collection::fromArray($array['table'] ?? $array['collection']). The use of size and default is straightforward and fits the base Attribute API.

As with Boolean, the nested database phpdoc doesn’t mention the type key, even though Collection::fromArray() now expects it. If that key is required for all inputs, updating the docblocks here (and in other attribute classes) would keep array-shape annotations accurate.

src/Migration/Transfer.php (1)

357-377: Remove unused variable.

Static analysis correctly identifies that $groupDatabasesIndex (line 360) is assigned but never used. Remove it unless you plan to implement additional grouping logic.

Apply this diff:

     public static function extractServices(array $services): array
     {
         $resources = [];
-        $groupDatabasesIndex = array_search(Transfer::GROUP_DATABASES, $services);
         foreach ($services as $service) {
             $resources = match ($service) {
src/Migration/Destinations/Appwrite.php (3)

799-807: Remove unused spatial support variable.

Static analysis correctly identifies that $supportForSpatialAttributes (line 799) is assigned but never used. The variable can be removed safely.

Apply this diff:

         $maxIndexLength = $dbForDatabases->getAdapter()->getMaxIndexLength();
         $internalIndexesKeys = $dbForDatabases->getAdapter()->getInternalIndexesKeys();
         $supportForIndexArray = $dbForDatabases->getAdapter()->getSupportForIndexArray();
-        $supportForSpatialAttributes = $dbForDatabases->getAdapter()->getSupportForSpatialAttributes();
         $supportForSpatialIndexNull = $dbForDatabases->getAdapter()->getSupportForSpatialIndexNull();

933-954: Remove unused loop variable.

The loop at line 935 declares $value but only uses $key. Use the underscore convention to indicate the value is intentionally unused.

Apply this diff:

                     foreach ($this->rowBuffer as $row) {
-                        foreach ($row as $key => $value) {
+                        foreach ($row as $key => $_) {
                             if (\str_starts_with($key, '$')) {
                                 continue;
                             }

1403-1493: Consider using extracted column size for validation.

In validateFieldsForIndexes(), the variable $columnSize (line 1465) is extracted but never used. If column size validation is needed for index creation, implement it; otherwise, remove the unused variable assignment.

If size validation is not needed, apply this diff:

             $columnStatus = $oldColumns[$columnIndex]['status'];
             $columnType = $oldColumns[$columnIndex]['type'];
-            $columnSize = $oldColumns[$columnIndex]['size'];
             $columnArray = $oldColumns[$columnIndex]['array'] ?? false;

If you plan to add size-based validation for indexes in the future, keep the variable and add a comment explaining the deferred validation.

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

865-914: Export flows for fields, indexes, and records: overall logic good after argument fix

Once the callback arity issue is fixed (see earlier comment), the refactored exportFields, exportIndexes, and exportRecords flows look consistent:

  • They rely on Resource::ENTITY_TYPE_RESOURCE_MAP and the cache keys to decide which entities to process.
  • The decision to treat VectorDB fields as attributes (getAttribute) while keeping others as columns (getColumn) matches the resource maps and the retrieved learnings about terminology vs underlying data.
  • The many‑to‑many handling in exportRecords remains scoped to schema‑based DBs and uses getSupportForAttributes() so it won't run against unsupported adapters.

No additional functional issues stand out here beyond the database type/name confusion already covered for getRecord().

Also applies to: 921-966, 974-1081


1499-1521: Helper factories getDatabase / getEntity / getRecord: types and mappings align with resource constants

These private helpers correctly centralize mapping between raw arrays and typed resources:

  • getDatabase() switches on the database type and returns Database, DocumentsDB, or VectorDB.
  • getEntity() switches on database type and returns Table or Collection.
  • getRecord() switches on database type and returns Row or Document.

With the earlier suggested change to pass Database::getType() into getEntity()/getRecord(), these helpers give you a clean single point for resource fan‑out when new database types are added.

Also applies to: 1539-1592


1593-1756: Column/attribute factory helpers look consistent with prior naming & relationship semantics

The new getColumn() and getAttribute() helpers:

  • Cover all relevant scalar and geometric types (string/email/enum/url/ip, bool, int, float/decimal, datetime, point/line/polygon, object, vector).
  • Correctly thread through required, default, array, size, min/max, and timestamps with sensible fallbacks.
  • Honour the existing relationship semantics by preferring relatedTable but falling back to relatedCollection, which matches the documented pattern of using legacy keys under the hood while exposing new terminology.
    This is in line with the retrieved learnings about gradual terminology migration in this codebase.

I don’t see functional issues here; these helpers should make future extensions much easier.

Also applies to: 1758-1920

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de06db2 and 105b48d.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • composer.json (1 hunks)
  • src/Migration/Cache.php (1 hunks)
  • src/Migration/Destinations/Appwrite.php (41 hunks)
  • src/Migration/Resource.php (3 hunks)
  • src/Migration/Resources/Database/Attribute.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Boolean.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/DateTime.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Decimal.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Email.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Enum.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/IP.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Integer.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Line.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/ObjectType.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Point.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Polygon.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Relationship.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Text.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/URL.php (1 hunks)
  • src/Migration/Resources/Database/Attribute/Vector.php (1 hunks)
  • src/Migration/Resources/Database/Collection.php (1 hunks)
  • src/Migration/Resources/Database/Column.php (1 hunks)
  • src/Migration/Resources/Database/Columns/ObjectType.php (1 hunks)
  • src/Migration/Resources/Database/Columns/Vector.php (1 hunks)
  • src/Migration/Resources/Database/DocumentsDB.php (1 hunks)
  • src/Migration/Resources/Database/VectorDB.php (1 hunks)
  • src/Migration/Sources/Appwrite.php (19 hunks)
  • src/Migration/Sources/Appwrite/Reader/Database.php (7 hunks)
  • src/Migration/Transfer.php (5 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Resources/Database/Column.php
  • src/Migration/Resources/Database/Columns/ObjectType.php
  • src/Migration/Resources/Database/Attribute/Line.php
  • src/Migration/Resources/Database/Attribute/Polygon.php
  • src/Migration/Resources/Database/Attribute/Text.php
  • src/Migration/Resources/Database/Attribute/Vector.php
  • src/Migration/Resources/Database/VectorDB.php
  • src/Migration/Resources/Database/DocumentsDB.php
  • src/Migration/Resource.php
  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Resources/Database/Attribute/Relationship.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Resources/Database/Attribute/ObjectType.php
  • src/Migration/Transfer.php
  • src/Migration/Resources/Database/Attribute.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Resources/Database/Attribute/Line.php
  • src/Migration/Cache.php
  • src/Migration/Resources/Database/DocumentsDB.php
  • src/Migration/Resource.php
  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Resources/Database/Attribute/Relationship.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Resources/Database/Attribute/ObjectType.php
  • src/Migration/Transfer.php
  • src/Migration/Resources/Database/Attribute.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-07-30T12:06:02.331Z
Learnt from: abnegate
Repo: utopia-php/migration PR: 0
File: :0-0
Timestamp: 2025-07-30T12:06:02.331Z
Learning: In the utopia-php/migration codebase, the Utopia Database package does not have a Memory adapter. When testing classes that require a Database instance (like CSV), use PHPUnit's createMock() method to create proper mocks instead of trying to instantiate real database adapters.

Applied to files:

  • src/Migration/Resources/Database/VectorDB.php
  • src/Migration/Resources/Database/DocumentsDB.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Resources/Database/Attribute/Relationship.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Sources/Appwrite.php
  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:45:36.026Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Resources/Database/Row.php:60-60
Timestamp: 2025-06-28T09:45:36.026Z
Learning: In the utopia-php/migration codebase, the `fromArray` method is not used on Row objects, so mismatches between `jsonSerialize()` output keys and `fromArray()` input expectations for Row class are not problematic.

Applied to files:

  • src/Migration/Resources/Database/Collection.php
  • src/Migration/Sources/Appwrite/Reader/Database.php
  • src/Migration/Destinations/Appwrite.php
📚 Learning: 2025-06-28T09:47:11.436Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite/Reader/API.php:8-8
Timestamp: 2025-06-28T09:47:11.436Z
Learning: In the Appwrite migration codebase, commented-out Tables service references (import statements and constructor parameters) are intentionally kept for future implementation when the Tables service becomes available in the Appwrite SDK, rather than being dead code that should be removed.

Applied to files:

  • src/Migration/Sources/Appwrite.php
🧬 Code graph analysis (26)
src/Migration/Resources/Database/Columns/ObjectType.php (3)
src/Migration/Resources/Database/Column.php (3)
  • Column (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Table.php (1)
  • Table (8-110)
src/Migration/Resources/Database/Columns/Vector.php (3)
  • __construct (10-28)
  • fromArray (61-72)
  • getType (74-77)
src/Migration/Resources/Database/Attribute/Email.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (7-50)
src/Migration/Resources/Database/Attribute/Text.php (3)
  • Text (9-97)
  • __construct (11-33)
  • getType (83-86)
src/Migration/Resources/Database/Attribute/Line.php (3)
src/Migration/Resources/Database/Attribute.php (2)
  • Attribute (8-158)
  • __construct (43-58)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Vector.php (2)
  • __construct (10-28)
  • fromArray (61-72)
src/Migration/Cache.php (2)
src/Migration/Resources/Database/Database.php (2)
  • getDatabase (99-102)
  • getType (94-97)
src/Migration/Resources/Database/Table.php (1)
  • getDatabase (91-94)
src/Migration/Resources/Database/Columns/Vector.php (4)
src/Migration/Resources/Database/Column.php (3)
  • Column (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Table.php (1)
  • Table (8-110)
src/Migration/Resources/Database/Attribute/Vector.php (4)
  • Vector (8-78)
  • __construct (10-28)
  • fromArray (61-72)
  • getType (74-77)
src/Migration/Resources/Database/Columns/ObjectType.php (3)
  • __construct (10-26)
  • fromArray (58-68)
  • getType (70-73)
src/Migration/Resources/Database/Attribute/Polygon.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Point.php (3)
  • __construct (10-26)
  • fromArray (58-68)
  • getType (70-73)
src/Migration/Resources/Database/Attribute/Point.php (2)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Text.php (2)
src/Migration/Resources/Database/Attribute.php (5)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
  • getSize (105-108)
  • getFormat (130-133)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Decimal.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Integer.php (5)
  • __construct (10-41)
  • fromArray (78-91)
  • getType (93-96)
  • getMin (98-101)
  • getMax (103-106)
src/Migration/Resources/Database/Attribute/Boolean.php (4)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Vector.php (3)
  • __construct (10-28)
  • fromArray (61-72)
  • getType (74-77)
src/Migration/Resources/Database/Attribute/DateTime.php (3)
  • __construct (10-29)
  • fromArray (67-78)
  • getType (31-34)
src/Migration/Resources/Database/Attribute/Vector.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Columns/Vector.php (4)
  • Vector (8-78)
  • __construct (10-28)
  • fromArray (61-72)
  • getType (74-77)
src/Migration/Resources/Database/Attribute/Integer.php (4)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/Vector.php (3)
  • __construct (10-28)
  • fromArray (61-72)
  • getType (74-77)
src/Migration/Resources/Database/Attribute/Decimal.php (5)
  • __construct (10-39)
  • fromArray (76-89)
  • getType (91-94)
  • getMin (96-99)
  • getMax (101-104)
src/Migration/Resources/Database/VectorDB.php (2)
src/Migration/Resource.php (1)
  • Resource (5-278)
src/Migration/Resources/Database/Collection.php (1)
  • fromArray (31-49)
src/Migration/Resources/Database/Attribute/DateTime.php (2)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/DocumentsDB.php (3)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • getName (74-77)
  • fromArray (45-57)
src/Migration/Resource.php (2)
  • Resource (5-278)
  • getName (150-150)
src/Migration/Resources/Database/Collection.php (2)
  • getName (9-12)
  • fromArray (31-49)
src/Migration/Resources/Database/Collection.php (5)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • getName (74-77)
  • fromArray (45-57)
src/Migration/Resource.php (2)
  • Resource (5-278)
  • getName (150-150)
src/Migration/Resources/Database/Table.php (3)
  • Table (8-110)
  • getName (81-84)
  • fromArray (50-62)
src/Migration/Resources/Database/DocumentsDB.php (3)
  • getName (9-12)
  • fromArray (25-37)
  • DocumentsDB (7-38)
src/Migration/Resources/Database/VectorDB.php (1)
  • VectorDB (7-38)
src/Migration/Resources/Database/Attribute/IP.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (7-50)
src/Migration/Resources/Database/Attribute/Text.php (3)
  • Text (9-97)
  • __construct (11-33)
  • getType (83-86)
src/Migration/Resources/Database/Attribute/Relationship.php (2)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Sources/Appwrite/Reader/Database.php (6)
src/Migration/Resources/Database/Database.php (3)
  • Database (19-103)
  • __construct (21-32)
  • getDatabase (99-102)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (7-50)
src/Migration/Resources/Database/Document.php (1)
  • Document (7-51)
src/Migration/Sources/Appwrite.php (2)
  • __construct (97-127)
  • getDatabase (1511-1521)
src/Migration/Resources/Database/Table.php (2)
  • __construct (20-32)
  • getDatabase (91-94)
src/Migration/Resource.php (1)
  • Resource (5-278)
src/Migration/Resources/Database/Attribute/ObjectType.php (2)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Transfer.php (1)
src/Migration/Resource.php (1)
  • Resource (5-278)
src/Migration/Resources/Database/Attribute/URL.php (3)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (1)
  • Collection (7-50)
src/Migration/Resources/Database/Attribute/Text.php (2)
  • Text (9-97)
  • getType (83-86)
src/Migration/Resources/Database/Attribute.php (3)
src/Migration/Resource.php (3)
  • Resource (5-278)
  • getName (150-150)
  • getGroup (152-152)
src/Migration/Transfer.php (2)
  • Transfer (5-378)
  • __construct (140-149)
src/Migration/Resources/Database/Table.php (1)
  • Table (8-110)
src/Migration/Sources/Appwrite.php (5)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-506)
  • report (49-157)
  • queryLimit (478-481)
  • queryEqual (423-426)
  • queryCursorAfter (434-476)
  • listDatabases (159-162)
  • getDatabase (40-47)
  • listTables (164-203)
  • listColumns (205-265)
  • listIndexes (267-312)
  • getSupportForAttributes (483-486)
  • querySelect (413-416)
  • listRows (314-365)
  • getRow (367-407)
src/Migration/Resources/Database/Database.php (5)
  • Database (19-103)
  • getDatabase (99-102)
  • getType (94-97)
  • getDatabaseName (84-87)
  • fromArray (45-57)
src/Migration/Resource.php (2)
  • Resource (5-278)
  • getId (169-172)
src/Migration/Sources/Appwrite/Reader.php (12)
  • report (21-21)
  • queryLimit (108-108)
  • queryEqual (92-92)
  • queryCursorAfter (100-100)
  • listDatabases (29-29)
  • listTables (38-38)
  • listColumns (47-47)
  • listIndexes (56-56)
  • getSupportForAttributes (109-109)
  • querySelect (83-83)
  • listRows (65-65)
  • getRow (75-75)
src/Migration/Sources/Appwrite/Reader/API.php (12)
  • report (28-118)
  • queryLimit (239-242)
  • queryEqual (221-224)
  • queryCursorAfter (230-237)
  • listDatabases (123-126)
  • listTables (131-138)
  • listColumns (146-154)
  • listIndexes (162-170)
  • getSupportForAttributes (244-247)
  • querySelect (211-214)
  • listRows (179-187)
  • getRow (196-205)
src/Migration/Destinations/Appwrite.php (3)
src/Migration/Sources/Appwrite/Reader/Database.php (4)
  • Database (23-506)
  • __construct (30-35)
  • getDatabase (40-47)
  • getSupportForAttributes (483-486)
src/Migration/Sources/Appwrite.php (3)
  • __construct (97-127)
  • getDatabase (1511-1521)
  • getName (129-132)
src/Migration/Cache.php (2)
  • __construct (24-27)
  • get (189-196)
src/Migration/Resources/Database/Attribute/Enum.php (2)
src/Migration/Resources/Database/Attribute.php (3)
  • Attribute (8-158)
  • __construct (43-58)
  • getType (88-88)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
🪛 PHPMD (2.15.0)
src/Migration/Transfer.php

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

(UnusedLocalVariable)

src/Migration/Destinations/Appwrite.php

799-799: Avoid unused local variables such as '$supportForSpatialAttributes'. (undefined)

(UnusedLocalVariable)


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

(UnusedLocalVariable)


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

(UnusedLocalVariable)

🔇 Additional comments (23)
composer.json (1)

29-29: Verify rationale for downgrading utopia-php/database from 4. to 3.*.*

This is a major version downgrade which is atypical. The AI summary indicates substantial architectural changes (per-database contexts, new resource types). Ensure this downgrade is intentional and that version 3.* provides all necessary functionality without security or stability concerns.

Please clarify:

  • Why is version 4.* incompatible with the new per-database migration architecture?
  • Does version 3.* lack features introduced in 4.* that could impact this PR or dependent code?
  • Are there any security considerations with using an older version?

Consider searching the PR description or related issues for context on this version constraint decision.

src/Migration/Resources/Database/Column.php (1)

25-26: New TYPE_OBJECT / TYPE_VECTOR constants are consistent

The added constants align with the new Column subclasses and mirror the Attribute type set; no issues spotted.

src/Migration/Resources/Database/Columns/ObjectType.php (1)

10-73: ObjectType column correctly wires into Column and legacy collection payloads

Constructor delegation looks correct, and fromArray() cleanly supports both table and legacy collection structures while routing through Table::fromArray(), matching the established migration pattern for terminology changes. getType() returning Column::TYPE_OBJECT is consistent with the new column type set. Based on learnings

src/Migration/Resources/Database/Columns/Vector.php (1)

10-77: Vector column implementation is consistent with the attribute/vector ecosystem

The constructor and fromArray() correctly mirror the existing Attribute\Vector pattern, including support for both table and legacy collection payloads, and getType() returning Column::TYPE_VECTOR ties it neatly into the new type set. Based on learnings

src/Migration/Resources/Database/Attribute.php (1)

8-157: New Attribute base class cleanly mirrors Column and supports the table/collection migration

The shared type constants, constructor shape, serialization, and getters are consistent with Column, and using Table as the associated entity while concrete subclasses accept Collection (a Table subclass) matches the intended terminology migration (table/column vs collection/attribute) without breaking legacy data flows. Based on learnings

src/Migration/Cache.php (1)

45-50: Including database type in TABLE/COLLECTION cache keys improves disambiguation

Prefixing the key with $resource->getDatabase()->getType() will prevent collisions between tables/collections that share sequences but live under different database types (e.g., traditional DB vs documents/vector DB), while leaving the rest of the cache behavior unchanged.

src/Migration/Resources/Database/Attribute/URL.php (1)

8-36: URL attribute specialization is correctly layered on top of Text

The constructor cleanly reuses Text while setting an appropriate default size and format: 'url', and getType() returning Attribute::TYPE_URL wires it into the Attribute type system as expected.

src/Migration/Resources/Database/Attribute/IP.php (1)

8-36: IP attribute implementation looks consistent with Text

The IP attribute correctly specializes Text by fixing format: 'ip' and returning Attribute::TYPE_IP from getType(). This matches the base Attribute/Text contracts and json serialization behavior.

No changes needed here from my side.

src/Migration/Resources/Database/Attribute/Line.php (1)

8-73: Line geometry attribute matches the common attribute pattern

Constructor, fromArray() (including table/collection handling), and getType() are all consistent with the other geometry/object attributes (Point, Polygon, ObjectType). Nothing stands out as problematic here.

src/Migration/Resources/Database/Attribute/Polygon.php (1)

8-73: Polygon geometry attribute implementation is consistent and straightforward

The constructor, fromArray() array shape, and getType() correctly mirror the pattern used by Point/Line, with TYPE_POLYGON as the discriminator. No issues spotted.

src/Migration/Resources/Database/Attribute/ObjectType.php (1)

8-73: ObjectType attribute cleanly integrates with the shared Attribute API

The constructor and fromArray() mirror the patterns used by other complex attributes and getType() correctly returns Attribute::TYPE_OBJECT. This should fit smoothly into the existing serialization/deserialization flow.

src/Migration/Resources/Database/Attribute/Point.php (1)

8-73: Point geometry attribute aligns with other spatial types

Constructor, fromArray() (including table/collection handling), and getType() follow the same conventions as Line and Polygon, with TYPE_POINT as the discriminator. Looks good.

src/Migration/Resources/Database/Attribute/Text.php (1)

1-97: LGTM! Attribute implementation follows established pattern.

The Text attribute class correctly extends Attribute, implements the required abstract methods, and handles backward compatibility with both 'table' and 'collection' keys in fromArray. The constructor properly forwards all parameters to the parent using named arguments.

src/Migration/Resources/Database/Attribute/Enum.php (1)

1-102: LGTM! Enum attribute implementation is consistent.

The Enum class correctly encapsulates enum-specific behavior by storing elements in formatOptions and provides proper accessor methods. The fromArray method handles backward compatibility with table/collection keys as expected.

src/Migration/Resources/Database/Attribute/DateTime.php (1)

1-79: LGTM! DateTime attribute is properly implemented.

The DateTime class correctly sets the datetime filter and implements all required methods following the established attribute pattern.

src/Migration/Resource.php (1)

33-130: LGTM! Resource type mappings correctly support multiple database backends.

The new database type constants (TYPE_DATABASE_DOCUMENTSDB, TYPE_DATABASE_VECTORDB) and their corresponding resource mappings enable the migration system to handle different database backends with appropriate entity/field/record terminology. The HACK comment on line 108 regarding the 'field' mapping for documentsdb is understood.

src/Migration/Resources/Database/Attribute/Relationship.php (1)

1-125: LGTM! Relationship attribute correctly implements terminology bridging.

The Relationship class properly handles the terminology transition by accepting relatedTable in the constructor but storing it as relatedCollection in the options (line 27), then providing fallback access in getRelatedTable() (line 98). This design pattern maintains compatibility with existing data sources while using modern terminology in the API surface.

Based on learnings.

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

49-505: Per-database context handling is well-implemented.

The enhanced report() method correctly enumerates databases, normalizes legacy types, and counts per-database resources using the appropriate database instances. The listRows(), getRow(), and countResources() methods properly route through per-database contexts when DatabaseResource is provided, falling back to the project database for metadata operations.

src/Migration/Transfer.php (1)

15-64: LGTM! New database-specific resource groups are well-organized.

The addition of granular database groups (GROUP_DATABASES_TABLES_DB, GROUP_DATABASES_DOCUMENTS_DB, GROUP_DATABASES_VECTOR_DB) and their corresponding resource arrays enables selective targeting of specific database backends during migration. The structure is clear and consistent.

src/Migration/Destinations/Appwrite.php (3)

62-111: Per-database context infrastructure properly initialized.

The constructor correctly accepts and stores both per-project ($dbForProject) and per-database resolution callables ($getDatabasesDB, $getDatabaseDSN), enabling the destination to route operations to the appropriate database instance based on context.


289-327: Resource routing correctly handles multiple database types.

The importDatabaseResource() method properly routes the new database resource types (TYPE_DATABASE_DOCUMENTSDB, TYPE_DATABASE_VECTORDB) and uses updated method names (createEntity, createField, createRecord) that align with the abstraction layer.


458-463: Correctly skip column creation for DocumentsDB.

The guard at line 460 properly uses getType() to detect DocumentsDB and skip column creation, which is appropriate since DocumentsDB doesn't require explicit column definitions.

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

295-303: Database reporting delegation looks correct

Delegating reportDatabases() to $this->reader->report() is a nice simplification and keeps API vs DB modes consistent. As long as both APIReader and DatabaseReader implement report() for the new database types, this should be fine.

@abnegate abnegate marked this pull request as ready for review November 21, 2025 04:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Migration/Sources/Appwrite.php (2)

825-838: Incorrect database name in nested payload.

Line 834 uses $databaseName for the 'name' field, but $databaseName is actually the database type (e.g., TYPE_DATABASE, TYPE_DATABASE_DOCUMENTSDB) passed from line 683, not the actual database name. This will result in the wrong value being stored.

Apply this diff to use the actual database name:

                         'database' => [
                             'id' => $database->getId(),
-                            'name' => $databaseName,
+                            'name' => $database->getDatabaseName(),
                             'type' => $database->getType(),
                             'database' => $database->getDatabase(),
                         ]

1050-1066: Wrong parameter passed to getRecord.

Line 1050 calls getRecord with $table->getDatabase()->getDatabaseName(), but getRecord expects the database type as the first parameter (per the signature at line 1580). This will cause incorrect routing between Document and Row resources.

Apply this diff to pass the database type:

-                    $row = self::getRecord($table->getDatabase()->getDatabaseName(), [
+                    $row = self::getRecord($table->getDatabase()->getType(), [
                         'id' => $id,
                         'table' => [
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 105b48d and 2321c28.

📒 Files selected for processing (2)
  • src/Migration/Sources/Appwrite.php (19 hunks)
  • src/Migration/Sources/CSV.php (8 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.
Learnt from: ArnabChatterjee20k
Repo: utopia-php/migration PR: 114
File: src/Migration/Resources/Database/Attribute/Decimal.php:10-38
Timestamp: 2025-11-20T13:07:39.272Z
Learning: In the utopia-php/migration codebase, the user ArnabChatterjee20k maintains consistency between parallel implementations: Attribute classes (e.g., Attribute/Decimal.php) mirror their corresponding Column classes (e.g., Columns/Decimal.php) in constructor defaults and behavior, even when design decisions might be questioned.
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.
📚 Learning: 2025-06-28T09:47:08.333Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite.php:843-851
Timestamp: 2025-06-28T09:47:08.333Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the class constructors and method parameters use the new terminology (like "relatedTable"), but the underlying data structures and API responses still use the legacy keys (like "relatedCollection"). This is an intentional design pattern to allow gradual migration while maintaining compatibility with existing data sources.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-07-30T12:06:02.331Z
Learnt from: abnegate
Repo: utopia-php/migration PR: 0
File: :0-0
Timestamp: 2025-07-30T12:06:02.331Z
Learning: In the utopia-php/migration codebase, the Utopia Database package does not have a Memory adapter. When testing classes that require a Database instance (like CSV), use PHPUnit's createMock() method to create proper mocks instead of trying to instantiate real database adapters.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:47:58.757Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Supabase.php:300-308
Timestamp: 2025-06-28T09:47:58.757Z
Learning: In the utopia-php/migration codebase, during the terminology swap from Collection/Attribute/Document to Table/Column/Row, the user ItzNotABug prefers to keep the existing query logic unchanged even if it becomes semantically incorrect with the new naming. The focus is purely on resource type renaming, not on fixing logical issues that become apparent after the terminology change.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-11-20T13:07:39.272Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/migration PR: 114
File: src/Migration/Resources/Database/Attribute/Decimal.php:10-38
Timestamp: 2025-11-20T13:07:39.272Z
Learning: In the utopia-php/migration codebase, the user ArnabChatterjee20k maintains consistency between parallel implementations: Attribute classes (e.g., Attribute/Decimal.php) mirror their corresponding Column classes (e.g., Columns/Decimal.php) in constructor defaults and behavior, even when design decisions might be questioned.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-07-02T06:24:50.844Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 81
File: src/Migration/Sources/CSV.php:215-233
Timestamp: 2025-07-02T06:24:50.844Z
Learning: In the utopia-php/migration codebase, invalid numeric values (like null) in parsed data are acceptable because the underlying database structure validator will catch and handle these validation errors, so explicit filtering during parsing is not required.

Applied to files:

  • src/Migration/Sources/CSV.php
📚 Learning: 2025-06-28T09:45:36.026Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Resources/Database/Row.php:60-60
Timestamp: 2025-06-28T09:45:36.026Z
Learning: In the utopia-php/migration codebase, the `fromArray` method is not used on Row objects, so mismatches between `jsonSerialize()` output keys and `fromArray()` input expectations for Row class are not problematic.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-11-20T13:12:43.446Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/migration PR: 114
File: src/Migration/Resources/Database/Collection.php:14-37
Timestamp: 2025-11-20T13:12:43.446Z
Learning: In the utopia-php/migration codebase, when Collection::fromArray is called, the input array is guaranteed to contain both the 'database' key and the nested 'type' key ($array['database']['type']), so defensive guards for these keys are not necessary.

Applied to files:

  • src/Migration/Sources/CSV.php
  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:47:11.436Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Sources/Appwrite/Reader/API.php:8-8
Timestamp: 2025-06-28T09:47:11.436Z
Learning: In the Appwrite migration codebase, commented-out Tables service references (import statements and constructor parameters) are intentionally kept for future implementation when the Tables service becomes available in the Appwrite SDK, rather than being dead code that should be removed.

Applied to files:

  • src/Migration/Sources/Appwrite.php
📚 Learning: 2025-06-28T09:45:57.650Z
Learnt from: ItzNotABug
Repo: utopia-php/migration PR: 80
File: src/Migration/Resources/Database/Columns/Relationship.php:86-89
Timestamp: 2025-06-28T09:45:57.650Z
Learning: In the utopia-php/migration codebase Relationship column class, the `getRelatedTable()` method intentionally returns `$this->options['relatedCollection']` (not `relatedTable`) because the underlying API still uses "collection" terminology, even though the internal codebase has been refactored to use "table" terminology.

Applied to files:

  • src/Migration/Sources/Appwrite.php
🧬 Code graph analysis (2)
src/Migration/Sources/CSV.php (6)
src/Migration/Resource.php (2)
  • Resource (5-278)
  • isSupported (154-167)
src/Migration/Sources/Appwrite.php (3)
  • Appwrite (72-1920)
  • getEntity (1539-1549)
  • getRecord (1580-1590)
src/Migration/Sources/Appwrite/Reader/Database.php (3)
  • listDatabases (159-162)
  • queryEqual (423-426)
  • queryLimit (478-481)
src/Migration/Sources/Appwrite/Reader/API.php (3)
  • listDatabases (123-126)
  • queryEqual (221-224)
  • queryLimit (239-242)
src/Migration/Sources/Appwrite/Reader.php (3)
  • listDatabases (29-29)
  • queryEqual (92-92)
  • queryLimit (108-108)
src/Migration/Resources/Database/Column.php (1)
  • Column (8-158)
src/Migration/Sources/Appwrite.php (7)
src/Migration/Sources/Appwrite/Reader/Database.php (14)
  • Database (23-506)
  • report (49-157)
  • queryLimit (478-481)
  • queryEqual (423-426)
  • queryCursorAfter (434-476)
  • listDatabases (159-162)
  • getDatabase (40-47)
  • listTables (164-203)
  • listColumns (205-265)
  • listIndexes (267-312)
  • getSupportForAttributes (483-486)
  • querySelect (413-416)
  • listRows (314-365)
  • getRow (367-407)
src/Migration/Resources/Database/Collection.php (2)
  • Collection (7-50)
  • fromArray (31-49)
src/Migration/Resources/Database/DocumentsDB.php (2)
  • DocumentsDB (7-38)
  • fromArray (25-37)
src/Migration/Resources/Database/Table.php (5)
  • Table (8-110)
  • getDatabase (91-94)
  • getTableName (96-99)
  • getRowSecurity (101-104)
  • fromArray (50-62)
src/Migration/Resources/Database/VectorDB.php (1)
  • VectorDB (7-38)
src/Migration/Resource.php (3)
  • Resource (5-278)
  • getId (169-172)
  • getPermissions (233-236)
src/Migration/Resources/Database/Column.php (4)
  • getType (88-88)
  • getTable (100-103)
  • Column (8-158)
  • getKey (95-98)
🔇 Additional comments (14)
src/Migration/Sources/CSV.php (6)

59-65: LGTM!

The addition of TYPE_DOCUMENT correctly expands support for document-based databases, aligning with the per-database context changes in this PR.


107-109: LGTM!

The use of isSupported() correctly checks whether the CSV source should handle any of the requested resources, properly utilizing the expanded resource type support.


136-149: LGTM!

The database lookup logic correctly queries for the target database, validates its existence, and normalizes legacy database types to the current type constants.


151-175: LGTM!

The payload construction correctly builds the nested database and table structures required by the fromArray methods, and the getEntity call properly routes to the appropriate resource type based on the database type.


340-347: LGTM!

The extended type parsing correctly handles the new geometric and vector types by decoding JSON representations, with appropriate null-safe fallback when decoding fails.


355-360: LGTM!

The getRecord call correctly constructs row/document resources with the appropriate payload structure, enabling proper routing between Row and Document types based on the database type.

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

97-127: LGTM!

The constructor correctly accepts the $getDatabasesDB callable as a required parameter before optional parameters, and properly initializes the reader based on the source type, enabling per-database context routing.


157-160: LGTM!

The addition of TYPE_DATABASE_DOCUMENTSDB and TYPE_DATABASE_VECTORDB to supported resources correctly expands the source's capabilities to handle document and vector databases.


299-302: LGTM!

The delegation to $this->reader->report() correctly abstracts the reporting logic, allowing different implementations for API and Database readers.


639-714: LGTM!

The refactored export logic elegantly handles multiple database types through DATABASE_TYPE_RESOURCE_MAP iteration and the helper closure pattern, making the code more maintainable and extensible for future database types.


757-764: Verify that database key exists in API mode.

Line 763 accesses $database['database'] without a null coalescing operator. If this key is missing (particularly in API mode), this will cause a PHP notice or error.

Apply this diff if the key might be absent:

-                        'database' => $database['database']
+                        'database' => $database['database'] ?? ''

1509-1519: LGTM!

The helper methods (getDatabase, getEntity, getRecord) correctly route to appropriate resource classes based on database type, providing a clean abstraction layer for per-database resource construction.

Also applies to: 1539-1549, 1580-1590


1592-1755: LGTM!

The getColumn method comprehensively handles all column types with appropriate parameter mapping, including proper fallback for legacy keys (e.g., relatedCollectionrelatedTable) and sensible defaults for missing optional fields.


1757-1919: LGTM!

The getAttribute method mirrors the getColumn implementation for document/vector database contexts, maintaining consistency across the parallel Column/Attribute implementations. Based on learnings.

@abnegate abnegate merged commit 18bd7d3 into main Nov 21, 2025
4 checks passed
@abnegate abnegate deleted the feat-documents-db branch November 21, 2025 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants