1
0
Fork 0
mirror of synced 2024-06-14 00:34:51 +12:00
appwrite/src/Appwrite/Migration/Migration.php

418 lines
12 KiB
PHP
Raw Normal View History

2021-01-14 05:51:02 +13:00
<?php
namespace Appwrite\Migration;
2024-03-07 06:34:21 +13:00
use Exception;
use Swoole\Runtime;
2024-03-07 06:34:21 +13:00
use Utopia\App;
use Utopia\CLI\Console;
2022-01-19 00:05:04 +13:00
use Utopia\Config\Config;
2024-03-07 06:34:21 +13:00
use Utopia\Database\Database;
use Utopia\Database\Document;
2022-12-15 04:42:25 +13:00
use Utopia\Database\Helpers\ID;
2024-03-07 06:34:21 +13:00
use Utopia\Database\Query;
2022-05-14 00:49:31 +12:00
use Utopia\Database\Validator\Authorization;
2021-01-14 05:51:02 +13:00
2022-09-09 04:46:18 +12:00
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
2021-01-14 05:51:02 +13:00
abstract class Migration
{
2021-01-19 03:43:55 +13:00
/**
* @var int
*/
protected int $limit = 100;
2021-12-09 06:50:04 +13:00
/**
2022-01-19 00:05:04 +13:00
* @var Document
2021-12-09 06:50:04 +13:00
*/
2022-01-19 00:05:04 +13:00
protected Document $project;
2021-01-19 03:43:55 +13:00
/**
2022-01-19 00:05:04 +13:00
* @var Database
2021-01-19 03:43:55 +13:00
*/
2022-01-19 00:05:04 +13:00
protected Database $projectDB;
2021-01-19 03:43:55 +13:00
/**
2022-01-19 00:05:04 +13:00
* @var Database
2021-01-19 03:43:55 +13:00
*/
2022-01-19 00:05:04 +13:00
protected Database $consoleDB;
2023-04-24 23:00:23 +12:00
/**
* @var \PDO
*/
2023-02-15 20:35:24 +13:00
protected \PDO $pdo;
2021-07-02 21:09:02 +12:00
/**
* @var array
*/
public static array $versions = [
2022-09-14 19:12:17 +12:00
'1.0.0-RC1' => 'V15',
2022-09-15 07:03:00 +12:00
'1.0.0' => 'V15',
'1.0.1' => 'V15',
2022-11-15 10:42:48 +13:00
'1.0.3' => 'V15',
2022-11-16 01:59:35 +13:00
'1.1.0' => 'V16',
2022-11-18 02:38:49 +13:00
'1.1.1' => 'V16',
2022-11-24 06:43:48 +13:00
'1.1.2' => 'V16',
2022-12-08 00:01:58 +13:00
'1.2.0' => 'V17',
2023-02-14 12:55:54 +13:00
'1.2.1' => 'V17',
2023-02-15 20:35:24 +13:00
'1.3.0' => 'V18',
2023-04-13 04:13:49 +12:00
'1.3.1' => 'V18',
2023-04-24 23:10:28 +12:00
'1.3.2' => 'V18',
2023-04-29 07:29:46 +12:00
'1.3.3' => 'V18',
2023-05-04 08:39:05 +12:00
'1.3.4' => 'V18',
2023-05-31 03:45:29 +12:00
'1.3.5' => 'V18',
2023-06-03 00:26:51 +12:00
'1.3.6' => 'V18',
2023-06-04 02:41:01 +12:00
'1.3.7' => 'V18',
2023-07-19 10:21:58 +12:00
'1.3.8' => 'V18',
2023-06-11 19:59:44 +12:00
'1.4.0' => 'V19',
2023-08-31 08:05:51 +12:00
'1.4.1' => 'V19',
2023-09-07 06:22:04 +12:00
'1.4.2' => 'V19',
2023-09-15 07:53:32 +12:00
'1.4.3' => 'V19',
2023-09-28 10:28:28 +13:00
'1.4.4' => 'V19',
2023-10-10 12:37:06 +13:00
'1.4.5' => 'V19',
2023-10-19 12:07:47 +13:00
'1.4.6' => 'V19',
2023-10-24 07:53:40 +13:00
'1.4.7' => 'V19',
2023-10-28 06:01:38 +13:00
'1.4.8' => 'V19',
2023-11-01 08:40:45 +13:00
'1.4.9' => 'V19',
2023-11-10 13:25:28 +13:00
'1.4.10' => 'V19',
'1.4.11' => 'V19',
2023-11-18 08:51:25 +13:00
'1.4.12' => 'V19',
'1.4.13' => 'V19',
2024-02-01 23:21:50 +13:00
'1.5.0' => 'V20',
2024-03-09 05:25:10 +13:00
'1.5.1' => 'V20',
2024-03-09 09:55:25 +13:00
'1.5.2' => 'V20',
2024-03-12 05:59:12 +13:00
'1.5.3' => 'V20',
2024-03-13 23:08:50 +13:00
'1.5.4' => 'V20',
2021-07-02 21:09:02 +12:00
];
2022-02-18 07:24:50 +13:00
/**
* @var array
*/
protected array $collections;
public function __construct()
{
2022-05-14 00:49:31 +12:00
Authorization::disable();
Authorization::setDefaultStatus(false);
2022-09-07 20:43:05 +12:00
$this->collections = Config::getParam('collections', []);
$projectCollections = $this->collections['projects'];
$this->collections['projects'] = array_merge([
2022-02-18 07:24:50 +13:00
'_metadata' => [
2022-08-14 22:33:36 +12:00
'$id' => ID::custom('_metadata'),
2022-05-14 00:49:31 +12:00
'$collection' => Database::METADATA
2022-02-18 07:24:50 +13:00
],
'audit' => [
2022-08-14 22:33:36 +12:00
'$id' => ID::custom('audit'),
2022-05-14 00:49:31 +12:00
'$collection' => Database::METADATA
2022-02-18 07:24:50 +13:00
],
'abuse' => [
2022-08-14 22:33:36 +12:00
'$id' => ID::custom('abuse'),
2022-05-14 00:49:31 +12:00
'$collection' => Database::METADATA
2022-02-18 07:24:50 +13:00
]
], $projectCollections);
2022-02-18 07:24:50 +13:00
}
/**
* Set project for migration.
*
2022-01-19 00:05:04 +13:00
* @param Document $project
* @param Database $projectDB
* @param Database $oldConsoleDB
*
2021-12-09 06:50:04 +13:00
* @return self
*/
2022-01-19 00:05:04 +13:00
public function setProject(Document $project, Database $projectDB, Database $consoleDB): self
{
$this->project = $project;
2022-01-19 00:05:04 +13:00
$this->projectDB = $projectDB;
$this->consoleDB = $consoleDB;
2021-12-09 06:50:04 +13:00
return $this;
}
2023-04-24 23:00:23 +12:00
/**
* Set PDO for Migration.
*
* @param \PDO $pdo
* @return \Appwrite\Migration\Migration
*/
2023-02-15 20:35:24 +13:00
public function setPDO(\PDO $pdo): self
{
$this->pdo = $pdo;
return $this;
}
/**
* Iterates through every document.
*
2021-01-19 03:43:55 +13:00
* @param callable $callback
*/
2021-01-21 22:57:15 +13:00
public function forEachDocument(callable $callback): void
{
$internalProjectId = $this->project->getInternalId();
$collections = match ($internalProjectId) {
'console' => $this->collections['console'],
default => $this->collections['projects'],
};
foreach ($collections as $collection) {
2022-05-24 02:54:50 +12:00
if ($collection['$collection'] !== Database::METADATA) {
2022-06-23 01:52:21 +12:00
continue;
2022-05-24 02:54:50 +12:00
}
2022-06-28 04:22:29 +12:00
2022-01-19 00:05:04 +13:00
Console::log('Migrating Collection ' . $collection['$id'] . ':');
2022-09-09 04:46:18 +12:00
\Co\run(function (array $collection, callable $callback) {
foreach ($this->documentsIterator($collection['$id']) as $document) {
go(function (Document $document, callable $callback) {
if (empty($document->getId()) || empty($document->getCollection())) {
return;
}
2022-01-19 00:05:04 +13:00
2022-09-09 04:46:18 +12:00
$old = $document->getArrayCopy();
$new = call_user_func($callback, $document);
2022-01-19 00:05:04 +13:00
2023-08-23 11:14:23 +12:00
if (is_null($new) || $new->getArrayCopy() == $old) {
2022-09-09 04:46:18 +12:00
return;
}
2022-09-09 04:46:18 +12:00
try {
2023-08-23 11:14:23 +12:00
$this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
2022-09-09 04:46:18 +12:00
} catch (\Throwable $th) {
Console::error('Failed to update document: ' . $th->getMessage());
return;
}
}, $document, $callback);
}
2022-09-09 04:46:18 +12:00
}, $collection, $callback);
}
}
2022-09-09 04:46:18 +12:00
/**
2022-09-10 00:44:04 +12:00
* Provides an iterator for all documents on a collection.
*
* @param string $collectionId
2022-09-09 04:46:18 +12:00
* @return iterable<Document>
2022-09-10 00:44:04 +12:00
* @throws \Exception
2022-09-09 04:46:18 +12:00
*/
public function documentsIterator(string $collectionId, $queries = []): iterable
2022-09-09 04:46:18 +12:00
{
$sum = 0;
$nextDocument = null;
$collectionCount = $this->projectDB->count($collectionId);
$queries[] = Query::limit($this->limit);
2022-09-09 04:46:18 +12:00
do {
if ($nextDocument !== null) {
$cursorQueryIndex = \array_search('cursorAfter', \array_map(fn (Query $query) => $query->getMethod(), $queries));
if ($cursorQueryIndex !== false) {
$queries[$cursorQueryIndex] = Query::cursorAfter($nextDocument);
} else {
$queries[] = Query::cursorAfter($nextDocument);
}
2022-09-09 04:46:18 +12:00
}
2022-09-09 04:46:18 +12:00
$documents = $this->projectDB->find($collectionId, $queries);
$count = count($documents);
$sum += $count;
2022-09-09 04:46:18 +12:00
Console::log($sum . ' / ' . $collectionCount);
foreach ($documents as $document) {
yield $document;
}
2022-01-19 00:05:04 +13:00
2022-09-09 04:46:18 +12:00
if ($count !== $this->limit) {
$nextDocument = null;
} else {
$nextDocument = end($documents);
}
} while (!is_null($nextDocument));
}
2022-05-14 00:49:31 +12:00
/**
* Creates colletion from the config collection.
*
* @param string $id
* @param string|null $name
* @return void
* @throws \Throwable
*/
protected function createCollection(string $id, string $name = null): void
{
$name ??= $id;
$collectionType = match ($this->project->getInternalId()) {
'console' => 'console',
default => 'projects',
};
2022-05-14 00:49:31 +12:00
if (!$this->projectDB->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), $name)) {
$attributes = [];
$indexes = [];
$collection = $this->collections[$collectionType][$id];
2022-05-14 00:49:31 +12:00
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
try {
$this->projectDB->createCollection($name, $attributes, $indexes);
} catch (\Throwable $th) {
throw $th;
}
}
}
2022-06-21 22:17:18 +12:00
/**
* Creates attribute from collections.php
*
* @param \Utopia\Database\Database $database
* @param string $collectionId
* @param string $attributeId
* @return void
* @throws \Exception
* @throws \Utopia\Database\Exception\Duplicate
* @throws \Utopia\Database\Exception\Limit
*/
2022-06-23 01:52:21 +12:00
public function createAttributeFromCollection(Database $database, string $collectionId, string $attributeId, string $from = null): void
2022-06-21 22:17:18 +12:00
{
2022-06-23 01:52:21 +12:00
$from ??= $collectionId;
2023-08-23 11:14:23 +12:00
$collectionType = match ($this->project->getInternalId()) {
'console' => 'console',
default => 'projects',
};
2023-08-23 11:14:23 +12:00
if ($from === 'files') {
$collectionType = 'buckets';
}
2023-08-23 11:14:23 +12:00
$collection = $this->collections[$collectionType][$from] ?? null;
2023-08-23 11:14:23 +12:00
2022-06-21 22:17:18 +12:00
if (is_null($collection)) {
throw new Exception("Collection {$from} not found");
2022-06-21 22:17:18 +12:00
}
2023-08-23 11:14:23 +12:00
2022-06-21 22:17:18 +12:00
$attributes = $collection['attributes'];
$attributeKey = array_search($attributeId, array_column($attributes, '$id'));
2022-06-22 01:46:05 +12:00
if ($attributeKey === false) {
2022-06-21 22:17:18 +12:00
throw new Exception("Attribute {$attributeId} not found");
}
$attribute = $attributes[$attributeKey];
2022-09-09 04:46:18 +12:00
$filters = $attribute['filters'] ?? [];
$default = $attribute['default'] ?? null;
2022-06-21 22:17:18 +12:00
$database->createAttribute(
collection: $collectionId,
id: $attributeId,
type: $attribute['type'],
size: $attribute['size'],
required: $attribute['required'] ?? false,
2022-09-09 04:46:18 +12:00
default: in_array('json', $filters) ? json_encode($default) : $default,
2022-06-21 22:17:18 +12:00
signed: $attribute['signed'] ?? false,
array: $attribute['array'] ?? false,
format: $attribute['format'] ?? '',
formatOptions: $attribute['formatOptions'] ?? [],
2022-09-09 04:46:18 +12:00
filters: $filters,
2022-06-21 22:17:18 +12:00
);
}
/**
* Creates index from collections.php
*
* @param \Utopia\Database\Database $database
* @param string $collectionId
* @param string $indexId
2022-09-09 04:46:18 +12:00
* @param string|null $from
2022-06-21 22:17:18 +12:00
* @return void
* @throws \Exception
* @throws \Utopia\Database\Exception\Duplicate
* @throws \Utopia\Database\Exception\Limit
*/
2022-09-09 04:46:18 +12:00
public function createIndexFromCollection(Database $database, string $collectionId, string $indexId, string $from = null): void
2022-06-21 22:17:18 +12:00
{
2022-09-09 04:46:18 +12:00
$from ??= $collectionId;
2023-08-23 11:14:23 +12:00
$collectionType = match ($this->project->getInternalId()) {
'console' => 'console',
default => 'projects',
};
2023-08-23 11:14:23 +12:00
$collection = $this->collections[$collectionType][$from] ?? null;
2022-06-21 22:17:18 +12:00
if (is_null($collection)) {
throw new Exception("Collection {$collectionId} not found");
}
2023-08-23 11:14:23 +12:00
2022-06-21 22:17:18 +12:00
$indexes = $collection['indexes'];
$indexKey = array_search($indexId, array_column($indexes, '$id'));
if ($indexKey === false) {
2023-08-22 12:12:10 +12:00
throw new Exception("Index {$indexId} not found");
2022-06-21 22:17:18 +12:00
}
$index = $indexes[$indexKey];
$database->createIndex(
collection: $collectionId,
id: $indexId,
type: $index['type'],
attributes: $index['attributes'],
lengths: $index['lengths'] ?? [],
orders: $index['orders'] ?? []
);
}
2023-02-15 20:35:24 +13:00
/**
* Change a collection attribute's internal type
*
* @param string $collection
* @param string $attribute
* @param string $type
* @return void
*/
protected function changeAttributeInternalType(string $collection, string $attribute, string $type): void
{
2023-12-15 02:32:06 +13:00
$stmt = $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$collection}` MODIFY `$attribute` $type;");
2023-02-15 20:35:24 +13:00
try {
$stmt->execute();
} catch (\Throwable $e) {
2023-02-15 20:35:24 +13:00
Console::warning($e->getMessage());
}
}
/**
* Executes migration for set project.
*/
abstract public function execute(): void;
2021-01-14 05:51:02 +13:00
}