Skip to content
Merged
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
77 changes: 77 additions & 0 deletions src/Migration/Resources/Database/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Utopia\Migration\Resources\Database;

use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Migration\Resource;
use Utopia\Migration\Transfer;

Expand Down Expand Up @@ -31,6 +32,82 @@ abstract class Column extends Resource
public const TYPE_OBJECT = 'object';
public const TYPE_VECTOR = 'vector';

/**
* Types whose size is fixed by the type itself. Appwrite leaves the size
* off the API response for these, so it has to be derived from the type.
*
* Mirrors Appwrite\Utopia\Database\Attribute::SIZES.
*
* @var array<string, int>
*/
public const SIZES = [
self::TYPE_TEXT => 65535,
self::TYPE_MEDIUMTEXT => 16777215,
self::TYPE_LONGTEXT => 2147483647,
];

/**
* String formats, mapped to the size Appwrite creates them with. Each
* format is also accepted as a shorthand type on an inline column
* definition, where it means a string of that format.
*
* Mirrors Appwrite\Utopia\Database\Attribute::FORMAT_SIZES.
*
* @var array<string, int>
*/
public const FORMAT_SIZES = [
self::TYPE_EMAIL => 254,
self::TYPE_ENUM => UtopiaDatabase::LENGTH_KEY,
self::TYPE_IP => 39,
self::TYPE_URL => 2000,
];

/**
* Size to fall back on for a varchar that arrives without one. Appwrite
* requires an explicit size for varchar, so this only guards a source
* that omits it.
*/
public const DEFAULT_VARCHAR_SIZE = 255;

/**
* Resolve a raw column definition into the type, format and size Appwrite
* stores for it. A format shorthand (`email`, `url`, `ip`, `enum`) becomes
* a string of that format, and an omitted size is filled in from the type
* or the format.
*
* Mirrors Appwrite\Utopia\Database\Attribute::resolve() so a column read
* from a source ends up with the size the destination would have stored.
*
* @param array<string, mixed> $column
* @return array{type: string, format: string, size: int}
*/
public static function resolve(array $column): array
{
$type = \is_string($column['type'] ?? null) ? $column['type'] : '';
$format = \is_string($column['format'] ?? null) ? $column['format'] : '';

if (isset(self::FORMAT_SIZES[$type])) {
$format = $type;
$type = self::TYPE_STRING;
}

$size = $column['size'] ?? null;
$size = \is_numeric($size) ? (int) $size : 0;

if (isset(self::SIZES[$type])) {
// Fixed width types ignore any size the source reported.
$size = self::SIZES[$type];
} elseif ($size < 1) {
$size = self::FORMAT_SIZES[$format] ?? $size;
}

return [
'type' => $type,
'format' => $format,
'size' => $size,
];
}

/**
* @param string $key
* @param Table $table
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 254,
int $size = Column::FORMAT_SIZES[Column::TYPE_EMAIL],
string $createdAt = '',
string $updatedAt = ''
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 256,
int $size = Column::FORMAT_SIZES[Column::TYPE_ENUM],
string $createdAt = '',
string $updatedAt = ''
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/IP.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 39,
int $size = Column::FORMAT_SIZES[Column::TYPE_IP],
string $createdAt = '',
string $updatedAt = ''
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/LongText.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 2147483647,
int $size = Column::SIZES[Column::TYPE_LONGTEXT],
string $format = '',
string $createdAt = '',
string $updatedAt = ''
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/MediumText.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 16777215,
int $size = Column::SIZES[Column::TYPE_MEDIUMTEXT],
string $format = '',
string $createdAt = '',
string $updatedAt = ''
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/RegularText.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 65535,
int $size = Column::SIZES[Column::TYPE_TEXT],
string $format = '',
string $createdAt = '',
string $updatedAt = ''
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/URL.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 2000,
int $size = Column::FORMAT_SIZES[Column::TYPE_URL],
string $createdAt = '',
string $updatedAt = ''
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Migration/Resources/Database/Columns/Varchar.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
bool $required = false,
?string $default = null,
bool $array = false,
int $size = 255,
int $size = Column::DEFAULT_VARCHAR_SIZE,
string $format = '',
string $createdAt = '',
string $updatedAt = ''
Expand Down
29 changes: 17 additions & 12 deletions src/Migration/Sources/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -3269,15 +3269,20 @@ public static function getRecord(string $databaseType, array $record): Resource

public static function getColumn(Table $table, mixed $column): Column
{
return match ($column['type']) {
Column::TYPE_STRING => match ($column['format'] ?? '') {
// Appwrite accepts a format (`email`, `url`, `ip`, `enum`) as a type on
// an inline column definition, and reports no size for the types whose
// size the type itself implies. Resolve both the way the server does.
['type' => $type, 'format' => $format, 'size' => $size] = Column::resolve($column);

return match ($type) {
Column::TYPE_STRING => match ($format) {
Column::TYPE_EMAIL => new Email(
$column['key'],
$table,
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 254,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3288,7 +3293,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? UtopiaDatabase::LENGTH_KEY,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3298,7 +3303,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 2000,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3308,7 +3313,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 39,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3318,7 +3323,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 0,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand Down Expand Up @@ -3446,7 +3451,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 255,
size: $size ?: Column::DEFAULT_VARCHAR_SIZE,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3457,7 +3462,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 65535,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3468,7 +3473,7 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 16777215,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),
Expand All @@ -3479,13 +3484,13 @@ public static function getColumn(Table $table, mixed $column): Column
required: $column['required'],
default: $column['default'],
array: $column['array'],
size: $column['size'] ?? 2147483647,
size: $size,
createdAt: $column['$createdAt'] ?? '',
updatedAt: $column['$updatedAt'] ?? '',
),


default => throw new \InvalidArgumentException("Unsupported column type: {$column['type']}"),
default => throw new \InvalidArgumentException("Unsupported column type: {$type}"),
};
}

Expand Down
109 changes: 109 additions & 0 deletions tests/Migration/Unit/Resources/ColumnTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Utopia\Tests\Unit\Resources;

use PHPUnit\Framework\TestCase;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Migration\Resources\Database\Column;

/**
* Lock-in for Column::resolve(), which mirrors the type/format/size mapping
* Appwrite applies in Appwrite\Utopia\Database\Attribute. If the two drift, a
* migrated column lands on the destination with a different size than the one
* the source project created it with.
*/
class ColumnTest extends TestCase
{
public function testFixedWidthTypesDeriveTheirSize(): void
{
$this->assertSame(
['type' => Column::TYPE_TEXT, 'format' => '', 'size' => 65535],
Column::resolve(['key' => 'body', 'type' => Column::TYPE_TEXT]),
);

$this->assertSame(
['type' => Column::TYPE_MEDIUMTEXT, 'format' => '', 'size' => 16777215],
Column::resolve(['key' => 'summary', 'type' => Column::TYPE_MEDIUMTEXT]),
);

$this->assertSame(
['type' => Column::TYPE_LONGTEXT, 'format' => '', 'size' => 2147483647],
Column::resolve(['key' => 'archive', 'type' => Column::TYPE_LONGTEXT]),
);
}

public function testFixedWidthTypesIgnoreAReportedSize(): void
{
$this->assertSame(
['type' => Column::TYPE_TEXT, 'format' => '', 'size' => 65535],
Column::resolve(['key' => 'body', 'type' => Column::TYPE_TEXT, 'size' => 128]),
);
}

public function testFormatShorthandsBecomeAString(): void
{
$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_EMAIL, 'size' => 254],
Column::resolve(['key' => 'email', 'type' => Column::TYPE_EMAIL]),
);

$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_URL, 'size' => 2000],
Column::resolve(['key' => 'website', 'type' => Column::TYPE_URL]),
);

$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_IP, 'size' => 39],
Column::resolve(['key' => 'address', 'type' => Column::TYPE_IP]),
);

$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_ENUM, 'size' => UtopiaDatabase::LENGTH_KEY],
Column::resolve(['key' => 'status', 'type' => Column::TYPE_ENUM]),
);
}

public function testFormattedStringWithoutSizeFallsBackToTheFormatSize(): void
{
$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_EMAIL, 'size' => 254],
Column::resolve([
'key' => 'email',
'type' => Column::TYPE_STRING,
'format' => Column::TYPE_EMAIL,
]),
);
}

public function testExplicitSizeWins(): void
{
$this->assertSame(
['type' => Column::TYPE_STRING, 'format' => Column::TYPE_EMAIL, 'size' => 512],
Column::resolve(['key' => 'email', 'type' => Column::TYPE_EMAIL, 'size' => 512]),
);

$this->assertSame(
['type' => Column::TYPE_VARCHAR, 'format' => '', 'size' => 64],
Column::resolve(['key' => 'slug', 'type' => Column::TYPE_VARCHAR, 'size' => 64]),
);

// A size that survived a round trip through a string stays a size.
$this->assertSame(
['type' => Column::TYPE_VARCHAR, 'format' => '', 'size' => 64],
Column::resolve(['key' => 'slug', 'type' => Column::TYPE_VARCHAR, 'size' => '64']),
);
}

public function testUnsizedAndUnknownDefinitionsResolveToZero(): void
{
$this->assertSame(
['type' => Column::TYPE_VARCHAR, 'format' => '', 'size' => 0],
Column::resolve(['key' => 'slug', 'type' => Column::TYPE_VARCHAR]),
);

$this->assertSame(
['type' => '', 'format' => '', 'size' => 0],
Column::resolve(['key' => 'unknown']),
);
}
}
Loading
Loading