From d4e32af792883202c047f81cfc9acce70889b908 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 30 Apr 2026 11:45:33 +0100 Subject: [PATCH] Migrate appwrite to OnDuplicate::Overwrite ('overwrite') MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer review on utopia-php/migration#171 renamed OnDuplicate::Upsert -> OnDuplicate::Overwrite (value 'upsert' -> 'overwrite') to align with Appwrite terms (skip / overwrite / fail). Applying the cross-repo ripple here: - app/controllers/api/migrations.php: 3 endpoint param descriptions updated ('upsert' -> 'overwrite' in the help text). The validator still uses OnDuplicate::values() so it auto-picks up the new value. - tests/e2e/Services/Migrations/MigrationsBase.php: all 'onDuplicate' => 'upsert' -> 'overwrite'; method names testAppwriteMigrationUpsert* -> testAppwriteMigrationOverwrite*; comments / assertion messages / local var names switched. - Left untouched: utopia's upsertDocuments operation, transaction TransactionState 'upsert' action, Operation validator — those refer to the database-level upsert primitive, not the OnDuplicate enum. composer.lock: utopia-php/migration 7d71505 -> b8ae7bc. --- app/controllers/api/migrations.php | 6 +- composer.lock | 8 +- .../Services/Migrations/MigrationsBase.php | 130 +++++++++--------- 3 files changed, 72 insertions(+), 72 deletions(-) diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index c9f810f353..73b93c4c5f 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -89,7 +89,7 @@ Http::post('/v1/migrations/appwrite') ->param('endpoint', '', new URL(), 'Source Appwrite endpoint') ->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject']) ->param('apiKey', '', new Text(512), 'Source API Key') - ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "upsert": replace existing row.', true) + ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true) ->inject('response') ->inject('dbForProject') ->inject('project') @@ -358,7 +358,7 @@ Http::post('/v1/migrations/csv/imports') ->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject']) ->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.') ->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true) - ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "upsert": replace existing row.', true) + ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') @@ -665,7 +665,7 @@ Http::post('/v1/migrations/json/imports') ->param('fileId', '', new UID(), 'File ID.') ->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.') ->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true) - ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "upsert": replace existing row.', true) + ->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') diff --git a/composer.lock b/composer.lock index 6ee740d807..191eba9608 100644 --- a/composer.lock +++ b/composer.lock @@ -4532,12 +4532,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "7d71505ec0a5731f322bbdc83cc9bee32053df2e" + "reference": "b8ae7bc953d1897994eaa0de7e677687a19e5133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/7d71505ec0a5731f322bbdc83cc9bee32053df2e", - "reference": "7d71505ec0a5731f322bbdc83cc9bee32053df2e", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/b8ae7bc953d1897994eaa0de7e677687a19e5133", + "reference": "b8ae7bc953d1897994eaa0de7e677687a19e5133", "shasum": "" }, "require": { @@ -4579,7 +4579,7 @@ "issues": "https://github.com/utopia-php/migration/issues", "source": "https://github.com/utopia-php/migration/tree/feat/skip-duplicates" }, - "time": "2026-04-28T07:10:11+00:00" + "time": "2026-04-30T10:24:12+00:00" }, { "name": "utopia-php/mongo", diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index 35dfad297d..0f54eff658 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -824,20 +824,20 @@ trait MigrationsBase $this->assertEquals(200, $rowAfterSkip['headers']['status-code']); $this->assertEquals('Mutated', $rowAfterSkip['body']['name'], 'onDuplicate=skip must not overwrite destination row'); - // Re-migration with onDuplicate=upsert — strict completion; destination + // Re-migration with onDuplicate=overwrite — strict completion; destination // row restored to source value. - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); - $rowAfterUpsert = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders); - $this->assertEquals(200, $rowAfterUpsert['headers']['status-code']); - $this->assertEquals('Original', $rowAfterUpsert['body']['name'], 'onDuplicate=upsert must restore source value'); + $rowAfterOverwrite = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders); + $this->assertEquals(200, $rowAfterOverwrite['headers']['status-code']); + $this->assertEquals('Original', $rowAfterOverwrite['body']['name'], 'onDuplicate=overwrite must restore source value'); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $destHeaders); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $sourceHeaders); @@ -846,7 +846,7 @@ trait MigrationsBase self::$cachedTableData = []; } - /** Unchanged source under Skip/Upsert is a no-op — every resource Tolerated. */ + /** Unchanged source under Skip/Overwrite is a no-op — every resource Tolerated. */ public function testAppwriteMigrationReRunIsIdempotent(): void { $sourceHeaders = [ @@ -900,16 +900,16 @@ trait MigrationsBase ]); $this->assertEquals('completed', $reRunSkip['status']); - // Re-run under Upsert: same unchanged source. Schema tolerance path + // Re-run under Overwrite: same unchanged source. Schema tolerance path // fires for each resource; rows go through DB-native upsert. - $reRunUpsert = $this->performMigrationSync([ + $reRunOverwrite = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $reRunUpsert['status']); + $this->assertEquals('completed', $reRunOverwrite['status']); foreach (['row-a', 'row-b'] as $rowId) { $check = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders); @@ -924,8 +924,8 @@ trait MigrationsBase self::$cachedTableData = []; } - /** Upsert reconciles container drift via UpdateInPlace; children (rows) preserved. */ - public function testAppwriteMigrationUpsertUpdatesContainerMetadata(): void + /** Overwrite reconciles container drift via UpdateInPlace; children (rows) preserved. */ + public function testAppwriteMigrationOverwriteUpdatesContainerMetadata(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -980,15 +980,15 @@ trait MigrationsBase 'enabled' => false, ]); - // Upsert re-migration: UpdateInPlace path fires for database + table. - $upsertResult = $this->performMigrationSync([ + // Overwrite re-migration: UpdateInPlace path fires for database + table. + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); // Assert dest database metadata reflects source's new values. $destDb = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId, $destHeaders); @@ -999,13 +999,13 @@ trait MigrationsBase $destTable = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId, $destHeaders); $this->assertEquals(200, $destTable['headers']['status-code']); $this->assertEquals('Renamed Source Table', $destTable['body']['name']); - $this->assertFalse($destTable['body']['enabled'], 'Upsert must propagate source enabled=false'); - $this->assertTrue($destTable['body']['documentSecurity'] ?? $destTable['body']['rowSecurity'], 'Upsert must propagate source rowSecurity=true'); + $this->assertFalse($destTable['body']['enabled'], 'Overwrite must propagate source enabled=false'); + $this->assertTrue($destTable['body']['documentSecurity'] ?? $destTable['body']['rowSecurity'], 'Overwrite must propagate source rowSecurity=true'); // Child row untouched — UpdateInPlace only rewrites container metadata. $row = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders); $this->assertEquals(200, $row['headers']['status-code']); - $this->assertEquals('SeedRow', $row['body']['name'], 'Upsert must not touch child rows when updating container metadata'); + $this->assertEquals('SeedRow', $row['body']['name'], 'Overwrite must not touch child rows when updating container metadata'); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $destHeaders); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $sourceHeaders); @@ -1089,8 +1089,8 @@ trait MigrationsBase self::$cachedTableData = []; } - /** Upsert drops dest columns source no longer declares; cleanup runs before rows land. */ - public function testAppwriteMigrationUpsertDropsOrphanColumn(): void + /** Overwrite drops dest columns source no longer declares; cleanup runs before rows land. */ + public function testAppwriteMigrationOverwriteDropsOrphanColumn(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1151,23 +1151,23 @@ trait MigrationsBase 'data' => ['name' => 'seed'], ]); - // Upsert re-migration: orphan_col must be dropped from dest. - $upsertResult = $this->performMigrationSync([ + // Overwrite re-migration: orphan_col must be dropped from dest. + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); // Orphan column dropped. $orphanCheck = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/orphan_col', $destHeaders); - $this->assertEquals(404, $orphanCheck['headers']['status-code'], 'Upsert must drop destination column source no longer declares'); + $this->assertEquals(404, $orphanCheck['headers']['status-code'], 'Overwrite must drop destination column source no longer declares'); // Source's column preserved. $nameCheck = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $destHeaders); - $this->assertEquals(200, $nameCheck['headers']['status-code'], 'Upsert must preserve columns source declared'); + $this->assertEquals(200, $nameCheck['headers']['status-code'], 'Overwrite must preserve columns source declared'); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $destHeaders); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $sourceHeaders); @@ -1176,7 +1176,7 @@ trait MigrationsBase self::$cachedTableData = []; } - /** Skip preserves orphan columns; cleanup is Upsert-only. */ + /** Skip preserves orphan columns; cleanup is Overwrite-only. */ public function testAppwriteMigrationSkipKeepsOrphanColumn(): void { $sourceHeaders = [ @@ -1253,7 +1253,7 @@ trait MigrationsBase } /** SDK-reachable attribute change propagates via updateAttributeInPlace; row data preserved. */ - public function testAppwriteMigrationUpsertUpdatesAttributeInPlace(): void + public function testAppwriteMigrationOverwriteUpdatesAttributeInPlace(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1320,14 +1320,14 @@ trait MigrationsBase $this->assertEquals('unknown', $r['body']['default']); }, 5000, 500); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); $this->assertEventually(function () use ($databaseId, $tableId, $destHeaders) { $r = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $destHeaders); @@ -1401,7 +1401,7 @@ trait MigrationsBase sleep(1); // Source advances strictly later (and to a different value). Under - // Upsert this would propagate to dest; under Skip it must not. + // Overwrite this would propagate to dest; under Skip it must not. $sourcePatch = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string/name', $sourceHeaders, [ 'required' => true, 'default' => null, @@ -1437,7 +1437,7 @@ trait MigrationsBase } /** Two-way onDelete change updates in place on both sides; partner meta refreshed by hand. */ - public function testAppwriteMigrationUpsertUpdatesRelationshipOnDeleteInPlace(): void + public function testAppwriteMigrationOverwriteUpdatesRelationshipOnDeleteInPlace(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1525,14 +1525,14 @@ trait MigrationsBase $this->assertEquals(Database::RELATION_MUTATE_RESTRICT, $r['body']['onDelete']); }, 5000, 500); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); // Both sides on dest must reflect onDelete=restrict. Asserting the // partner side is the regression guard for the previously-missed @@ -1559,7 +1559,7 @@ trait MigrationsBase } /** Two-way recreate with same spec: spec-match guard tolerates parent; pair-key dedup tolerates partner. Both sides + child rows preserved. */ - public function testAppwriteMigrationUpsertTwoWayRecreateSkipsPartnerSide(): void + public function testAppwriteMigrationOverwriteTwoWayRecreateSkipsPartnerSide(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1670,14 +1670,14 @@ trait MigrationsBase ]); $this->assertEquals(200, $relink['headers']['status-code']); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); $this->assertEventually(function () use ($databaseId, $destHeaders) { $parent = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/parents/columns/kids', $destHeaders); @@ -1704,7 +1704,7 @@ trait MigrationsBase } /** One-way + onDelete change falls through to DropAndRecreate (in-place gated off for one-way). */ - public function testAppwriteMigrationUpsertOneWayRelationshipDropAndRecreate(): void + public function testAppwriteMigrationOverwriteOneWayRelationshipDropAndRecreate(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1781,14 +1781,14 @@ trait MigrationsBase $this->assertEquals(Database::RELATION_MUTATE_RESTRICT, $r['body']['onDelete']); }, 5000, 500); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); $this->assertEventually(function () use ($databaseId, $destHeaders) { $r = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/parents/columns/kids', $destHeaders); @@ -1807,7 +1807,7 @@ trait MigrationsBase } /** Recreate with non-SDK spec change (array toggle): updateAttributeInPlace bails → drop+recreate; row pass refills. */ - public function testAppwriteMigrationUpsertAttributeRecreateDropsAndRecreates(): void + public function testAppwriteMigrationOverwriteAttributeRecreateDropsAndRecreates(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1882,14 +1882,14 @@ trait MigrationsBase ]); $this->assertEquals(200, $relink['headers']['status-code']); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); $this->assertEventually(function () use ($databaseId, $tableId, $destHeaders) { $col = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $destHeaders); @@ -1911,7 +1911,7 @@ trait MigrationsBase } /** Source drops+recreates with SAME spec: spec-match guard forces Tolerate; dest meta untouched. */ - public function testAppwriteMigrationUpsertSameSpecRecreateTolerates(): void + public function testAppwriteMigrationOverwriteSameSpecRecreateTolerates(): void { $sourceHeaders = [ 'content-type' => 'application/json', @@ -1984,14 +1984,14 @@ trait MigrationsBase ]); $this->assertEquals(200, $relink['headers']['status-code']); - $upsertResult = $this->performMigrationSync([ + $overwriteResult = $this->performMigrationSync([ 'resources' => $resources, 'endpoint' => $this->webEndpoint, 'projectId' => $this->getProject()['$id'], 'apiKey' => $this->getProject()['apiKey'], - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); - $this->assertEquals('completed', $upsertResult['status']); + $this->assertEquals('completed', $overwriteResult['status']); // Spec-match guard fired → dest column's $createdAt stayed at the // first-migration value. If DropAndRecreate had run, $createdAt @@ -2002,7 +2002,7 @@ trait MigrationsBase $this->assertEquals(100, $destAfter['body']['size']); $this->assertTrue($destAfter['body']['required']); - // Row pass under Upsert still propagated source's new row value. + // Row pass under Overwrite still propagated source's new row value. $rowAfter = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders); $this->assertEquals(200, $rowAfter['headers']['status-code']); $this->assertEquals('after-recreate', $rowAfter['body']['name']); @@ -2884,7 +2884,7 @@ trait MigrationsBase } /** - * onDuplicate=upsert on re-import: existing rows are replaced with imported values. + * onDuplicate=overwrite on re-import: existing rows are replaced with imported values. */ public function testCreateCSVImportOverwrite(): void { @@ -2905,7 +2905,7 @@ trait MigrationsBase $this->assertEquals(100, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']); }, 10_000, 500); - // Mutate one row so we can prove upsert restores it to the CSV's original value + // Mutate one row so we can prove overwrite restores it to the CSV's original value $mutate = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2916,12 +2916,12 @@ trait MigrationsBase $this->assertEquals(200, $mutate['headers']['status-code']); $this->assertEquals(22, $mutate['body']['age']); - // Second import with onDuplicate=upsert: mutated row restored to CSV value + // Second import with onDuplicate=overwrite: mutated row restored to CSV value $second = $this->performCsvMigration([ 'fileId' => $fileId, 'bucketId' => $bucketId, 'resourceId' => $databaseId . ':' . $tableId, - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); $this->assertEventually(function () use ($second) { $migration = $this->client->call(Client::METHOD_GET, '/migrations/' . $second['body']['$id'], array_merge([ @@ -2931,14 +2931,14 @@ trait MigrationsBase $this->assertEquals('completed', $migration['body']['status']); }, 10_000, 500); - // Mutated row is back to CSV's original age (proving upsert actually replaced the row) + // Mutated row is back to CSV's original age (proving overwrite actually replaced the row) $row = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(200, $row['headers']['status-code']); $this->assertEquals($originalName, $row['body']['name']); - $this->assertEquals($originalAge, $row['body']['age'], 'onDuplicate=upsert must restore row to imported value'); + $this->assertEquals($originalAge, $row['body']['age'], 'onDuplicate=overwrite must restore row to imported value'); // Row count still 100 (no duplicates created) $rows = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ @@ -3141,7 +3141,7 @@ trait MigrationsBase } /** - * onDuplicate=upsert on JSON re-import: existing rows replaced with imported values. + * onDuplicate=overwrite on JSON re-import: existing rows replaced with imported values. */ public function testCreateJSONImportOverwrite(): void { @@ -3175,7 +3175,7 @@ trait MigrationsBase 'fileId' => $fileId, 'bucketId' => $bucketId, 'resourceId' => $databaseId . ':' . $tableId, - 'onDuplicate' => 'upsert', + 'onDuplicate' => 'overwrite', ]); $this->assertEventually(function () use ($second) { $migration = $this->client->call(Client::METHOD_GET, '/migrations/' . $second['body']['$id'], array_merge([ @@ -3191,7 +3191,7 @@ trait MigrationsBase ], $this->getHeaders())); $this->assertEquals(200, $row['headers']['status-code']); $this->assertEquals($originalName, $row['body']['name']); - $this->assertEquals($originalAge, $row['body']['age'], 'onDuplicate=upsert must restore row to imported value'); + $this->assertEquals($originalAge, $row['body']['age'], 'onDuplicate=overwrite must restore row to imported value'); $rows = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ 'content-type' => 'application/json',