Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 68 additions & 43 deletions src/Migration/Destinations/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ class Appwrite extends Destination
*/
private array $rowBuffer = [];

/**
* The rows whose documents are in $rowBuffer.
*
* @var array<Row>
*/
private array $rowBufferResources = [];

/**
* Overwrite-mode orphan tracking, keyed by (database, table). Orphans are
* destination keys not in `attributeKeys` / `indexKeys`. Entries removed
Expand Down Expand Up @@ -300,6 +307,7 @@ public function run(
private function resetRunState(): void
{
$this->rowBuffer = [];
$this->rowBufferResources = [];
$this->orphansByTable = [];
$this->processedTwoWayPairs = [];
$this->provisioningDatabases = [];
Expand Down Expand Up @@ -549,6 +557,7 @@ protected function import(array $resources, callable $callback): void
}

$total = \count($resources);
$records = [];

foreach ($resources as $index => $resource) {
$resource->setStatus(Resource::STATUS_PROCESSING);
Expand Down Expand Up @@ -590,9 +599,17 @@ protected function import(array $resources, callable $callback): void
$this->dbForPlatform->setPreserveDates(false);
}

// Rows are counted once their batch is written, since writing it can still fail them.
if ($responseResource instanceof Row) {
$records[] = $responseResource;
continue;
}

$this->cache->update($responseResource);
}

$this->cache->updateAll($records);

$callback($resources);
}

Expand Down Expand Up @@ -1552,13 +1569,7 @@ protected function createIndex(Index $resource): bool
return true;
}

/**
* @throws AuthorizationException
* @throws DatabaseException
* @throws StructureException
* @throws Exception
*/
protected function createRecord(Row $resource, bool $isLast): bool
private function bufferRecord(Row $resource): bool
{
if ($resource->getId() == 'unique()') {
$resource->setId(ID::unique());
Expand Down Expand Up @@ -1622,8 +1633,25 @@ protected function createRecord(Row $resource, bool $isLast): bool
'$id' => $resource->getId(),
'$permissions' => $resource->getPermissions(),
], $data));
$this->rowBufferResources[] = $resource;

return true;
}

/**
* Rows are written in one batch, so a failed write fails every row in it.
*
* @throws AuthorizationException
* @throws DatabaseException
* @throws StructureException
* @throws Exception
*/
protected function createRecord(Row $resource, bool $isLast): bool
{
$buffered = $this->bufferRecord($resource);

if ($isLast) {
// Write the batch even when its last row was rejected, or the rows before it are lost.
if ($isLast && !empty($this->rowBuffer)) {
try {
$database = $this->dbForProject->getDocument(
self::META_DATABASES,
Expand Down Expand Up @@ -1669,48 +1697,45 @@ protected function createRecord(Row $resource, bool $isLast): bool
}
$collectionId = $this->tableCollectionId($database, $table);

try {
match ($this->onDuplicate) {
OnDuplicate::Overwrite => $dbForDatabases->skipRelationshipsExistCheck(
fn () => $dbForDatabases->upsertDocuments($collectionId, $this->rowBuffer)
),
OnDuplicate::Skip => $dbForDatabases->skipDuplicates(
fn () => $dbForDatabases->skipRelationshipsExistCheck(
fn () => $dbForDatabases->createDocuments($collectionId, $this->rowBuffer)
)
),
OnDuplicate::Fail => $dbForDatabases->skipRelationshipsExistCheck(
match ($this->onDuplicate) {
OnDuplicate::Overwrite => $dbForDatabases->skipRelationshipsExistCheck(
fn () => $dbForDatabases->upsertDocuments($collectionId, $this->rowBuffer)
),
OnDuplicate::Skip => $dbForDatabases->skipDuplicates(
fn () => $dbForDatabases->skipRelationshipsExistCheck(
fn () => $dbForDatabases->createDocuments($collectionId, $this->rowBuffer)
),
};
} catch (DuplicateException $e) {
$resource->setStatus(Resource::STATUS_ERROR, 'Document already exists');
$this->addError(new Exception(
resourceName: $resource->getName(),
resourceGroup: $resource->getGroup(),
resourceId: $resource->getId(),
message: 'Document already exists',
previous: $e,
));
return false;
} catch (StructureException $e) {
$resource->setStatus(Resource::STATUS_ERROR, $e->getMessage());
$this->addError(new Exception(
resourceName: $resource->getName(),
resourceGroup: $resource->getGroup(),
resourceId: $resource->getId(),
message: $e->getMessage(),
previous: $e,
));
return false;
)
),
OnDuplicate::Fail => $dbForDatabases->skipRelationshipsExistCheck(
fn () => $dbForDatabases->createDocuments($collectionId, $this->rowBuffer)
),
};
} catch (\Throwable $e) {
$message = $e instanceof DuplicateException ? 'Document already exists' : $e->getMessage();

foreach ($this->rowBufferResources as $row) {
$row->setStatus(Resource::STATUS_ERROR, $message);
}

if (!$e instanceof DuplicateException && !$e instanceof StructureException) {
throw $e;
}

$this->addError(new Exception(
resourceName: $resource->getName(),
resourceGroup: $resource->getGroup(),
resourceId: $resource->getId(),
message: $message,
previous: $e,
));
return false;
} finally {
$this->rowBuffer = [];
$this->rowBufferResources = [];
}
}


return true;
return $buffered;
}

/** Relationships route through deleteRelationship since deleteAttribute throws for VAR_RELATIONSHIP. */
Expand Down
12 changes: 9 additions & 3 deletions src/Migration/Destinations/CSV.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,14 @@ protected function writeCSVLine($handle, array $fields): bool
$parts = [];

foreach ($fields as $field) {
if ($field === null) {
$parts[] = 'null';
continue;
}

$field = (string)$field;
if (\strpbrk($field, $this->delimiter . "\n\r" . $this->enclosure) !== false) {
// Quoted, the text "null" is not read back as NULL.
if ($field === 'null' || \strpbrk($field, $this->delimiter . "\n\r" . $this->enclosure) !== false) {
$parts[] = $this->enclosure . \str_replace($this->enclosure, $this->enclosure . $this->enclosure, $field) . $this->enclosure;
} else {
$parts[] = $field;
Expand Down Expand Up @@ -316,10 +322,10 @@ protected function resourceToCSVData(Row $resource): array
/**
* Convert a single value to CSV-compatible format
*/
protected function convertValueToCSV(mixed $value): string
protected function convertValueToCSV(mixed $value): ?string
{
if (\is_null($value)) {
return 'null';
return null;
}
if (\is_bool($value)) {
return $value ? 'true' : 'false';
Expand Down
Loading
Loading