From 46c755336c657ae0ab2b8a01aed9732359c3d417 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 10:28:29 +0530 Subject: [PATCH 1/9] bump: dependencies. --- composer.json | 2 +- composer.lock | 76 +++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/composer.json b/composer.json index 4c36ab125d..2065139bb9 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.18.*", - "utopia-php/migration": "0.10.*", + "utopia-php/migration": "0.12.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", "utopia-php/pools": "0.8.*", diff --git a/composer.lock b/composer.lock index 72b460ec52..052644ba5b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "55bc52686a08d64930e6af7411ac0654", + "content-hash": "25ac1acb960988af5c10239c3bde258b", "packages": [ { "name": "adhocore/jwt", @@ -69,16 +69,16 @@ }, { "name": "appwrite/appwrite", - "version": "11.1.0", + "version": "15.0.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3" + "reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3", - "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/deb97b62e0abed8a4fd5c5d48e77365cf89867cf", + "reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf", "shasum": "" }, "require": { @@ -104,10 +104,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/15.0.0", "url": "https://appwrite.io/support" }, - "time": "2024-06-26T07:03:23+00:00" + "time": "2025-05-18T09:47:10+00:00" }, { "name": "appwrite/php-clamav", @@ -3493,16 +3493,16 @@ }, { "name": "utopia-php/database", - "version": "0.71.9", + "version": "0.71.10", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "eb2f759020bba617e99dd67973a9bd949b47f54e" + "reference": "83278d663f9c63c25a11d9e71c7922da7ec48636" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/eb2f759020bba617e99dd67973a9bd949b47f54e", - "reference": "eb2f759020bba617e99dd67973a9bd949b47f54e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/83278d663f9c63c25a11d9e71c7922da7ec48636", + "reference": "83278d663f9c63c25a11d9e71c7922da7ec48636", "shasum": "" }, "require": { @@ -3543,9 +3543,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.71.9" + "source": "https://github.com/utopia-php/database/tree/0.71.10" }, - "time": "2025-07-02T16:37:41+00:00" + "time": "2025-07-18T00:05:55+00:00" }, { "name": "utopia-php/detector", @@ -3993,20 +3993,20 @@ }, { "name": "utopia-php/migration", - "version": "0.10.4", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "0c85917482db172b3ccdc0704e42af3c1cc89361" + "reference": "973a4daa283f711a104e9bb7cf5a79dec2988df0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/0c85917482db172b3ccdc0704e42af3c1cc89361", - "reference": "0c85917482db172b3ccdc0704e42af3c1cc89361", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/973a4daa283f711a104e9bb7cf5a79dec2988df0", + "reference": "973a4daa283f711a104e9bb7cf5a79dec2988df0", "shasum": "" }, "require": { - "appwrite/appwrite": "11.*", + "appwrite/appwrite": "15.*", "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", @@ -4043,9 +4043,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.10.4" + "source": "https://github.com/utopia-php/migration/tree/0.12.0" }, - "time": "2025-07-02T18:31:09+00:00" + "time": "2025-07-17T12:20:11+00:00" }, { "name": "utopia-php/orchestration", @@ -4810,16 +4810,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.41.11", + "version": "0.41.15", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "60122cb613a5a1c82667ecc2217e351654a8d404" + "reference": "02a7e0df5a555c7bdfb50cb68db60460afb409e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/60122cb613a5a1c82667ecc2217e351654a8d404", - "reference": "60122cb613a5a1c82667ecc2217e351654a8d404", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/02a7e0df5a555c7bdfb50cb68db60460afb409e1", + "reference": "02a7e0df5a555c7bdfb50cb68db60460afb409e1", "shasum": "" }, "require": { @@ -4855,9 +4855,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.41.11" + "source": "https://github.com/appwrite/sdk-generator/tree/0.41.15" }, - "time": "2025-07-04T09:56:24+00:00" + "time": "2025-07-16T13:00:21+00:00" }, { "name": "doctrine/annotations", @@ -5084,16 +5084,16 @@ }, { "name": "laravel/pint", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "9ab851dba4faa51a3c3223dd3d07044129021024" + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/9ab851dba4faa51a3c3223dd3d07044129021024", - "reference": "9ab851dba4faa51a3c3223dd3d07044129021024", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", "shasum": "" }, "require": { @@ -5104,7 +5104,7 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.76.0", + "friendsofphp/php-cs-fixer": "^3.82.2", "illuminate/view": "^11.45.1", "larastan/larastan": "^3.5.0", "laravel-zero/framework": "^11.45.0", @@ -5149,7 +5149,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-03T10:37:47+00:00" + "time": "2025-07-10T18:09:32+00:00" }, { "name": "matthiasmullie/minify", @@ -5276,16 +5276,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -5324,7 +5324,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -5332,7 +5332,7 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nikic/php-parser", From 12f8374378599644e425ac4a2a70540637c6124d Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 10:32:35 +0530 Subject: [PATCH 2/9] update: response modal. --- src/Appwrite/Utopia/Response/Model/MigrationReport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/MigrationReport.php b/src/Appwrite/Utopia/Response/Model/MigrationReport.php index 15164d56fc..56d17d3f7f 100644 --- a/src/Appwrite/Utopia/Response/Model/MigrationReport.php +++ b/src/Appwrite/Utopia/Response/Model/MigrationReport.php @@ -29,9 +29,9 @@ class MigrationReport extends Model 'default' => 0, 'example' => 20, ]) - ->addRule(Resource::TYPE_DOCUMENT, [ + ->addRule(Resource::TYPE_ROW, [ 'type' => self::TYPE_INTEGER, - 'description' => 'Number of documents to be migrated.', + 'description' => 'Number of rows to be migrated.', 'default' => 0, 'example' => 20, ]) From 40a619084415643348bcbd601d5fc85d0738b38f Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 10:55:30 +0530 Subject: [PATCH 3/9] update: log message with fallbacks. --- app/controllers/shared/api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 76fe177b0b..bbfd3f8533 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -801,8 +801,8 @@ App::shutdown() Console::info("Triggering database event: \n" . \json_encode([ 'projectId' => $project->getId(), 'databaseId' => $queueForDatabase->getDatabase()?->getId(), - 'collectionId' => $queueForDatabase->getCollection()?->getId(), - 'documentId' => $queueForDatabase->getDocument()?->getId(), + 'tableId' => $queueForDatabase->getTable()?->getId() ?? $queueForDatabase->getCollection()?->getId(), + 'rowId' => $queueForDatabase->getRow()?->getId() ?? $queueForDatabase->getDocument()?->getId(), ])); $queueForDatabase->trigger(); } From 9b1f5eb905c47347cdbd23de4fa692637c6ca017 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 10:55:48 +0530 Subject: [PATCH 4/9] update: tests. --- .../Services/Migrations/MigrationsBase.php | 170 ++++++++++-------- tests/unit/Migration/MigrationTest.php | 1 + 2 files changed, 92 insertions(+), 79 deletions(-) diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index b8b9439e64..1910d6d59f 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -436,25 +436,25 @@ trait MigrationsBase /** * @depends testAppwriteMigrationDatabase */ - public function testAppwriteMigrationDatabasesCollection(array $data): array + public function testAppwriteMigrationDatabasesTable(array $data): array { $databaseId = $data['databaseId']; - $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + $table = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'collectionId' => ID::unique(), - 'name' => 'Test Collection', + 'tableId' => ID::unique(), + 'name' => 'Test Table', ]); - $this->assertEquals(201, $collection['headers']['status-code']); + $this->assertEquals(201, $table['headers']['status-code']); - $collectionId = $collection['body']['$id']; + $tableId = $table['body']['$id']; - // Create Attribute - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + // Create Column + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables/' . $tableId . '/columns/string', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -467,9 +467,9 @@ trait MigrationsBase $this->assertEquals(202, $response['headers']['status-code']); - // Wait for attribute to be ready - $this->assertEventually(function () use ($databaseId, $collectionId) { - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + // Wait for column to be ready + $this->assertEventually(function () use ($databaseId, $tableId) { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $tableId . '/columns/name', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -482,8 +482,8 @@ trait MigrationsBase $result = $this->performMigrationSync([ 'resources' => [ Resource::TYPE_DATABASE, - Resource::TYPE_COLLECTION, - Resource::TYPE_ATTRIBUTE, + Resource::TYPE_TABLE, + Resource::TYPE_COLUMN, ], 'endpoint' => 'http://localhost/v1', 'projectId' => $this->getProject()['$id'], @@ -491,9 +491,9 @@ trait MigrationsBase ]); $this->assertEquals('completed', $result['status']); - $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE], $result['resources']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_TABLE, Resource::TYPE_COLUMN], $result['resources']); - foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] as $resource) { + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_TABLE, Resource::TYPE_COLUMN] as $resource) { $this->assertArrayHasKey($resource, $result['statusCounters']); $this->assertEquals(0, $result['statusCounters'][$resource]['error']); $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); @@ -502,7 +502,7 @@ trait MigrationsBase $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); } - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, [ + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $tableId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getDestinationProject()['$id'], 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], @@ -511,10 +511,10 @@ trait MigrationsBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); - $this->assertEquals($collectionId, $response['body']['$id']); - $this->assertEquals('Test Collection', $response['body']['name']); + $this->assertEquals($tableId, $response['body']['$id']); + $this->assertEquals('Test Table', $response['body']['name']); - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $tableId . '/columns/name', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getDestinationProject()['$id'], 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], @@ -536,41 +536,41 @@ trait MigrationsBase return [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, ]; } /** - * @depends testAppwriteMigrationDatabasesCollection + * @depends testAppwriteMigrationDatabasesTable */ - public function testAppwriteMigrationDatabasesDocument(array $data): void + public function testAppwriteMigrationDatabasesRow(array $data): void { + $table = $data['tableId']; $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [ + $row = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables/' . $table . '/rows', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'documentId' => ID::unique(), + 'rowId' => ID::unique(), 'data' => [ - 'name' => 'Test Document', + 'name' => 'Test Row', ] ]); - $this->assertEquals(201, $document['headers']['status-code']); - $this->assertNotEmpty($document['body']); - $this->assertNotEmpty($document['body']['$id']); + $this->assertEquals(201, $row['headers']['status-code']); + $this->assertNotEmpty($row['body']); + $this->assertNotEmpty($row['body']['$id']); - $documentId = $document['body']['$id']; + $rowId = $row['body']['$id']; $result = $this->performMigrationSync([ 'resources' => [ Resource::TYPE_DATABASE, - Resource::TYPE_COLLECTION, - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_DOCUMENT, + Resource::TYPE_TABLE, + Resource::TYPE_COLUMN, + Resource::TYPE_ROW, ], 'endpoint' => 'http://localhost/v1', 'projectId' => $this->getProject()['$id'], @@ -586,10 +586,10 @@ trait MigrationsBase ]); $this->assertEquals('completed', $result['status']); - $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT], $result['resources']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_TABLE, Resource::TYPE_COLUMN, Resource::TYPE_ROW], $result['resources']); - //TODO: Add TYPE_DOCUMENT to the migration status counters once pending issue is resolved - foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] as $resource) { + // TODO: Add TYPE_ROW to the migration status counters once pending issue is resolved + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_TABLE, Resource::TYPE_COLUMN] as $resource) { $this->assertArrayHasKey($resource, $result['statusCounters']); $this->assertEquals(0, $result['statusCounters'][$resource]['error']); $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); @@ -598,7 +598,7 @@ trait MigrationsBase $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); } - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $table . '/rows/' . $rowId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getDestinationProject()['$id'], 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], @@ -607,8 +607,8 @@ trait MigrationsBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); - $this->assertEquals($documentId, $response['body']['$id']); - $this->assertEquals('Test Document', $response['body']['name']); + $this->assertEquals($rowId, $response['body']['$id']); + $this->assertEquals('Test Row', $response['body']['name']); // Cleanup $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ @@ -920,23 +920,23 @@ trait MigrationsBase $databaseId = $response['body']['$id']; - // make a collection - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + // make a table + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'name' => 'Test collection', - 'collectionId' => ID::unique(), + 'name' => 'Test table', + 'tableId' => ID::unique(), ]); $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals($response['body']['name'], 'Test collection'); + $this->assertEquals($response['body']['name'], 'Test table'); - $collectionId = $response['body']['$id']; + $tableId = $response['body']['$id']; - // make attributes - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ + // make columns + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -952,7 +952,7 @@ trait MigrationsBase $this->assertEquals($response['body']['size'], 256); $this->assertEquals($response['body']['required'], true); - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/tables/' . $tableId . '/columns/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1026,16 +1026,16 @@ trait MigrationsBase $fileIds[$label] = $response['body']['$id']; } - // missing attribute, fail in worker. + // missing column, fail in worker. $missingColumn = $this->performCsvMigration( [ 'fileId' => $fileIds['missing-column'], 'bucketId' => $bucketIds['missing-column'], - 'resourceId' => $databaseId . ':' . $collectionId, + 'resourceId' => $databaseId . ':' . $tableId, ] ); - $this->assertEventually(function () use ($missingColumn, $databaseId, $collectionId) { + $this->assertEventually(function () use ($missingColumn, $databaseId, $tableId) { $migrationId = $missingColumn['body']['$id']; $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([ 'content-type' => 'application/json', @@ -1047,11 +1047,15 @@ trait MigrationsBase $this->assertEquals('failed', $migration['body']['status']); $this->assertEquals('CSV', $migration['body']['source']); $this->assertEquals('Appwrite', $migration['body']['destination']); - $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']); + $this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']); $this->assertEmpty($migration['body']['statusCounters']); + + $errorJson = $migration['body']['errors'][0]; + $errorData = json_decode($errorJson, true); + $this->assertThat( - implode("\n", $migration['body']['errors']), - $this->stringContains("CSV header mismatch. Missing attribute: 'age'") + $errorData['message'], + $this->stringContains("CSV header mismatch. Missing column: 'age'") ); }, 60000, 500); @@ -1060,11 +1064,11 @@ trait MigrationsBase [ 'fileId' => $fileIds['missing-row'], 'bucketId' => $bucketIds['missing-row'], - 'resourceId' => $databaseId . ':' . $collectionId, + 'resourceId' => $databaseId . ':' . $tableId, ] ); - $this->assertEventually(function () use ($missingColumn, $databaseId, $collectionId) { + $this->assertEventually(function () use ($missingColumn, $databaseId, $tableId) { $migrationId = $missingColumn['body']['$id']; $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([ 'content-type' => 'application/json', @@ -1076,10 +1080,14 @@ trait MigrationsBase $this->assertEquals('failed', $migration['body']['status']); $this->assertEquals('CSV', $migration['body']['source']); $this->assertEquals('Appwrite', $migration['body']['destination']); - $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']); + $this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']); $this->assertEmpty($migration['body']['statusCounters']); + + $errorJson = $migration['body']['errors'][0]; + $errorData = json_decode($errorJson, true); + $this->assertThat( - implode("\n", $migration['body']['errors']), + $errorData['message'], $this->stringContains('CSV row does not match the number of header columns') ); }, 60000, 500); @@ -1089,11 +1097,11 @@ trait MigrationsBase [ 'fileId' => $fileIds['irrelevant-column'], 'bucketId' => $bucketIds['irrelevant-column'], - 'resourceId' => $databaseId . ':' . $collectionId, + 'resourceId' => $databaseId . ':' . $tableId, ] ); - $this->assertEventually(function () use ($irrelevantColumn, $databaseId, $collectionId) { + $this->assertEventually(function () use ($irrelevantColumn, $databaseId, $tableId) { $migrationId = $irrelevantColumn['body']['$id']; $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([ 'content-type' => 'application/json', @@ -1105,11 +1113,15 @@ trait MigrationsBase $this->assertEquals('failed', $migration['body']['status']); $this->assertEquals('CSV', $migration['body']['source']); $this->assertEquals('Appwrite', $migration['body']['destination']); - $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']); + $this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']); $this->assertEmpty($migration['body']['statusCounters']); + + $errorJson = $migration['body']['errors'][0]; + $errorData = json_decode($errorJson, true); + $this->assertThat( - implode("\n", $migration['body']['errors']), - $this->stringContains("CSV header mismatch. Unexpected attribute: 'email'") + $errorData['message'], + $this->stringContains("CSV header mismatch. Unexpected column: 'email'") ); }, 60000, 500); @@ -1119,7 +1131,7 @@ trait MigrationsBase 'endpoint' => 'http://localhost/v1', 'fileId' => $fileIds['default'], 'bucketId' => $bucketIds['default'], - 'resourceId' => $databaseId . ':' . $collectionId, + 'resourceId' => $databaseId . ':' . $tableId, ] ); @@ -1127,11 +1139,11 @@ trait MigrationsBase $this->assertEquals('CSV', $migration['body']['source']); $this->assertEquals('pending', $migration['body']['status']); $this->assertEquals('Appwrite', $migration['body']['destination']); - $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']); + $this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']); return [ 'databaseId' => $databaseId, - 'collectionId' => $collectionId, + 'tableId' => $tableId, 'migrationId' => $migration['body']['$id'], ]; } @@ -1141,14 +1153,14 @@ trait MigrationsBase */ public function testImportSuccessful(array $response): void { + $tableId = $response['tableId']; $databaseId = $response['databaseId']; - $collectionId = $response['collectionId']; $migrationId = $response['migrationId']; - $documentsCountInCSV = 100; + $rowsCountInCSV = 100; // get migration stats - $this->assertEventually(function () use ($migrationId, $databaseId, $collectionId, $documentsCountInCSV) { + $this->assertEventually(function () use ($migrationId, $databaseId, $tableId, $rowsCountInCSV) { $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1159,13 +1171,13 @@ trait MigrationsBase $this->assertEquals('completed', $migration['body']['status']); $this->assertEquals('CSV', $migration['body']['source']); $this->assertEquals('Appwrite', $migration['body']['destination']); - $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']); - $this->assertArrayHasKey(Resource::TYPE_DOCUMENT, $migration['body']['statusCounters']); - $this->assertEquals($documentsCountInCSV, $migration['body']['statusCounters'][Resource::TYPE_DOCUMENT]['success']); - }, 60000, 500); + $this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']); + $this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']); + $this->assertEquals($rowsCountInCSV, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']); + }, 1000, 500); - // get documents count - $documents = $this->client->call(Client::METHOD_GET, '/databases/'.$databaseId.'/collections/'.$collectionId.'/documents', array_merge([ + // get rows count + $rows = $this->client->call(Client::METHOD_GET, '/databases/'.$databaseId.'/tables/'.$tableId.'/rows', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1175,10 +1187,10 @@ trait MigrationsBase ] ]); - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertIsArray($documents['body']['documents']); - $this->assertIsNumeric($documents['body']['total']); - $this->assertEquals($documentsCountInCSV, $documents['body']['total']); + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertIsArray($rows['body']['rows']); + $this->assertIsNumeric($rows['body']['total']); + $this->assertEquals($rowsCountInCSV, $rows['body']['total']); } private function performCsvMigration(array $body): array diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php index 536278d55b..bb6c49d2fc 100644 --- a/tests/unit/Migration/MigrationTest.php +++ b/tests/unit/Migration/MigrationTest.php @@ -41,6 +41,7 @@ abstract class MigrationTest extends TestCase foreach (Migration::$versions as $class) { $this->assertTrue(class_exists('Appwrite\\Migration\\Version\\' . $class)); } + // Test if current version exists // Only test official releases - skip if latest is release candidate if (!(\str_contains(APP_VERSION_STABLE, 'RC'))) { From dca9a17e7a79413853d0c1eb1220684862f43aed Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 11:36:47 +0530 Subject: [PATCH 5/9] fix: `null` permissions. --- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 7a077fed0d..0bf01d8573 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -74,7 +74,7 @@ class Upsert extends Action ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documentId', '', new CustomId(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.') - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') From d3726f9a551bc308fddce8a42d05ef689c373bb3 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 11:37:47 +0530 Subject: [PATCH 6/9] fix: `null` permissions on tables api as well. --- .../Modules/Databases/Http/Databases/Tables/Rows/Upsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php index 42a79e8a72..25794ec4a3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php @@ -61,7 +61,7 @@ class Upsert extends DocumentUpsert ->param('tableId', '', new UID(), 'Table ID.') ->param('rowId', '', new UID(), 'Row ID.') ->param('data', [], new JSON(), 'Row data as JSON object. Include all required columns of the row to be created or updated.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') From 5a44f6bcea1e3411a4540a3ce94bc7b59cee02e2 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 12:03:46 +0530 Subject: [PATCH 7/9] update: maintain consistency for permissions with a null check. --- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 4 ++-- .../Modules/Databases/Http/Databases/Tables/Rows/Upsert.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 0bf01d8573..28c57cea15 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -74,7 +74,7 @@ class Upsert extends Action ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documentId', '', new CustomId(), 'Document ID.') ->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.') - ->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') @@ -133,7 +133,7 @@ class Upsert extends Action } $data['$id'] = $documentId; - $data['$permissions'] = $permissions; + $data['$permissions'] = \is_null($permissions) ? [] : $permissions; $newDocument = new Document($data); $operations = 0; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php index 25794ec4a3..42a79e8a72 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Tables/Rows/Upsert.php @@ -61,7 +61,7 @@ class Upsert extends DocumentUpsert ->param('tableId', '', new UID(), 'Table ID.') ->param('rowId', '', new UID(), 'Row ID.') ->param('data', [], new JSON(), 'Row data as JSON object. Include all required columns of the row to be created or updated.', true) - ->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) + ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') From d6ec18bda750a185dcb7ed2f66b785a2277cf660 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 12:14:02 +0530 Subject: [PATCH 8/9] update: maintain tests consistency. --- .../Databases/Tables/DatabasesBase.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/e2e/Services/Databases/Tables/DatabasesBase.php b/tests/e2e/Services/Databases/Tables/DatabasesBase.php index 29f92cc58a..de54f3ce04 100644 --- a/tests/e2e/Services/Databases/Tables/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Tables/DatabasesBase.php @@ -1984,6 +1984,36 @@ trait DatabasesBase ]); $this->assertEquals(2, $rows['body']['total']); + + // test without passing permissions + $row = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/tables/' . $data['moviesId'] . '/rows/' . $rowId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'title' => 'Thor: Ragnarok', + 'releaseYear' => 2000 + ] + ]); + + $this->assertEquals(200, $row['headers']['status-code']); + $this->assertEquals('Thor: Ragnarok', $row['body']['title']); + + $row = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $data['moviesId'] . '/rows/' . $rowId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $row['headers']['status-code']); + + $deleteResponse = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/tables/' . $data['moviesId'] . '/rows/' . $rowId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(204, $deleteResponse['headers']['status-code']); } /** From 5b60eb8b947f6e26f09fd7495409af85973c4699 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 18 Jul 2025 12:44:51 +0530 Subject: [PATCH 9/9] update: code quality. Co-authored-by: Jake Barnby --- .../Databases/Http/Databases/Collections/Documents/Upsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 28c57cea15..09e59c2ead 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -133,7 +133,7 @@ class Upsert extends Action } $data['$id'] = $documentId; - $data['$permissions'] = \is_null($permissions) ? [] : $permissions; + $data['$permissions'] = $permissions ?? []; $newDocument = new Document($data); $operations = 0;