From 21ad5239e17eab3e12f6d48040240a9e24817203 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 28 Mar 2024 14:57:59 +0100 Subject: [PATCH] Fixes --- .gitignore | 1 + .vscode/launch.json | 14 ++ composer.json | 8 +- phpunit.xml | 23 +++ src/Internal/Driver/MysqlArray.php | 8 + src/Internal/Driver/PostgresArray.php | 8 +- src/Internal/Driver/RedisArray.php | 13 +- .../Serializer/ByteaSerializer.php | 2 +- src/{ => Internal}/Serializer/IntString.php | 4 +- src/{ => Internal}/Serializer/Passthrough.php | 4 +- src/Settings/SqlSettings.php | 2 +- tests/OrmTest.php | 174 ++++++++++++++++++ 12 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 phpunit.xml rename src/{ => Internal}/Serializer/ByteaSerializer.php (97%) rename src/{ => Internal}/Serializer/IntString.php (95%) rename src/{ => Internal}/Serializer/Passthrough.php (96%) create mode 100644 tests/OrmTest.php diff --git a/.gitignore b/.gitignore index 4ef8091..31d0de8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.phpunit.cache /vendor/ *.cache composer.lock diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..542060d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + } + ] +} \ No newline at end of file diff --git a/composer.json b/composer.json index 9a7fb6f..68cc435 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,11 @@ "danog\\AsyncOrm\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "danog\\AsyncOrm\\Test\\": "tests/" + } + }, "authors": [ { "name": "Alexander Pankratov", @@ -29,7 +34,8 @@ "vimeo/psalm": "dev-master", "phpunit/phpunit": "^11.0.6", "amphp/php-cs-fixer-config": "^2.0.1", - "friendsofphp/php-cs-fixer": "^3.51" + "friendsofphp/php-cs-fixer": "^3.51", + "amphp/process": "^2.0" }, "scripts": { "cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v" diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..0e66c06 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Internal/Driver/MysqlArray.php b/src/Internal/Driver/MysqlArray.php index 7783767..21ea1c5 100644 --- a/src/Internal/Driver/MysqlArray.php +++ b/src/Internal/Driver/MysqlArray.php @@ -25,6 +25,8 @@ use AssertionError; use danog\AsyncOrm\Driver\Mysql; use danog\AsyncOrm\Driver\SqlArray; use danog\AsyncOrm\FieldConfig; +use danog\AsyncOrm\Internal\Serializer\IntString; +use danog\AsyncOrm\Internal\Serializer\Passthrough; use danog\AsyncOrm\KeyType; use danog\AsyncOrm\Serializer; use danog\AsyncOrm\ValueType; @@ -56,6 +58,12 @@ final class MysqlArray extends SqlArray */ public function __construct(FieldConfig $config, Serializer $serializer) { + /** @var Serializer */ + $serializer = match ($config->valueType) { + ValueType::INT => new IntString, + ValueType::STRING => new Passthrough, + default => $serializer + }; $settings = $config->settings; \assert($settings instanceof \danog\AsyncOrm\Settings\Mysql); diff --git a/src/Internal/Driver/PostgresArray.php b/src/Internal/Driver/PostgresArray.php index 0c97911..1fe6b6c 100644 --- a/src/Internal/Driver/PostgresArray.php +++ b/src/Internal/Driver/PostgresArray.php @@ -22,9 +22,9 @@ use Amp\Postgres\PostgresConnectionPool; use Amp\Sync\LocalKeyedMutex; use danog\AsyncOrm\Driver\SqlArray; use danog\AsyncOrm\FieldConfig; +use danog\AsyncOrm\Internal\Serializer\ByteaSerializer; use danog\AsyncOrm\KeyType; use danog\AsyncOrm\Serializer; -use danog\AsyncOrm\Serializer\ByteaSerializer; use danog\AsyncOrm\Settings\Postgres; use danog\AsyncOrm\ValueType; use Revolt\EventLoop; @@ -104,7 +104,7 @@ class PostgresArray extends SqlArray ); "); - $result = $this->db->query("DESCRIBE \"bytea_{$config->table}\""); + $result = $connection->query("DESCRIBE \"bytea_{$config->table}\""); while ($column = $result->fetchRow()) { ['Field' => $key, 'Type' => $type, 'Null' => $null] = $column; $type = \strtoupper($type); @@ -116,11 +116,11 @@ class PostgresArray extends SqlArray } elseif ($key === 'value') { $expected = $valueType; } else { - $this->db->query("ALTER TABLE \"bytea_{$config->table}\" DROP \"$key\""); + $connection->query("ALTER TABLE \"bytea_{$config->table}\" DROP \"$key\""); continue; } if ($expected !== $type || $null !== 'NO') { - $this->db->query("ALTER TABLE \"bytea_{$config->table}\" MODIFY \"$key\" $expected NOT NULL"); + $connection->query("ALTER TABLE \"bytea_{$config->table}\" MODIFY \"$key\" $expected NOT NULL"); } } diff --git a/src/Internal/Driver/RedisArray.php b/src/Internal/Driver/RedisArray.php index 2998fcb..24508ba 100644 --- a/src/Internal/Driver/RedisArray.php +++ b/src/Internal/Driver/RedisArray.php @@ -23,9 +23,9 @@ use Amp\Redis\RedisClient; use Amp\Sync\LocalKeyedMutex; use danog\AsyncOrm\Driver\DriverArray; use danog\AsyncOrm\FieldConfig; +use danog\AsyncOrm\Internal\Serializer\IntString; +use danog\AsyncOrm\Internal\Serializer\Passthrough; use danog\AsyncOrm\Serializer; -use danog\AsyncOrm\Serializer\IntString; -use danog\AsyncOrm\Serializer\Passthrough; use danog\AsyncOrm\Settings\Redis; use danog\AsyncOrm\ValueType; use Revolt\EventLoop; @@ -54,9 +54,12 @@ final class RedisArray extends DriverArray */ public function __construct(FieldConfig $config, Serializer $serializer) { - if ($serializer instanceof Passthrough && $config->valueType === ValueType::INT) { - $serializer = new IntString; - } + /** @var Serializer */ + $serializer = match ($config->valueType) { + ValueType::INT => new IntString, + ValueType::STRING => new Passthrough, + default => $serializer + }; parent::__construct($config, $serializer); self::$mutex ??= new LocalKeyedMutex; diff --git a/src/Serializer/ByteaSerializer.php b/src/Internal/Serializer/ByteaSerializer.php similarity index 97% rename from src/Serializer/ByteaSerializer.php rename to src/Internal/Serializer/ByteaSerializer.php index da10897..a9b8b27 100644 --- a/src/Serializer/ByteaSerializer.php +++ b/src/Internal/Serializer/ByteaSerializer.php @@ -16,7 +16,7 @@ * @link https://daniil.it/AsyncOrm AsyncOrm documentation */ -namespace danog\AsyncOrm\Serializer; +namespace danog\AsyncOrm\Internal\Serializer; use Amp\Postgres\PostgresByteA; use danog\AsyncOrm\Serializer; diff --git a/src/Serializer/IntString.php b/src/Internal/Serializer/IntString.php similarity index 95% rename from src/Serializer/IntString.php rename to src/Internal/Serializer/IntString.php index ecff47d..ad5eaa9 100644 --- a/src/Serializer/IntString.php +++ b/src/Internal/Serializer/IntString.php @@ -16,14 +16,14 @@ * @link https://daniil.it/AsyncOrm AsyncOrm documentation */ -namespace danog\AsyncOrm\Serializer; +namespace danog\AsyncOrm\Internal\Serializer; use danog\AsyncOrm\Serializer; /** * Integer casting serializer. * - * @api + * @internal * * @implements Serializer */ diff --git a/src/Serializer/Passthrough.php b/src/Internal/Serializer/Passthrough.php similarity index 96% rename from src/Serializer/Passthrough.php rename to src/Internal/Serializer/Passthrough.php index 2cd3dea..36299d5 100644 --- a/src/Serializer/Passthrough.php +++ b/src/Internal/Serializer/Passthrough.php @@ -16,14 +16,14 @@ * @link https://daniil.it/AsyncOrm AsyncOrm documentation */ -namespace danog\AsyncOrm\Serializer; +namespace danog\AsyncOrm\Internal\Serializer; use danog\AsyncOrm\Serializer; /** * Passthrough serializer. * - * @api + * @internal * * @template TValue * @implements Serializer diff --git a/src/Settings/SqlSettings.php b/src/Settings/SqlSettings.php index d8bd02b..c9f225f 100644 --- a/src/Settings/SqlSettings.php +++ b/src/Settings/SqlSettings.php @@ -50,7 +50,7 @@ abstract readonly class SqlSettings extends DriverSettings public function __construct( /** @var T */ public readonly SqlConfig $config, - Serializer $serializer, + ?Serializer $serializer, int $cacheTtl = self::DEFAULT_CACHE_TTL, int $maxConnections = self::DEFAULT_SQL_MAX_CONNECTIONS, int $idleTimeout = self::DEFAULT_SQL_IDLE_TIMEOUT, diff --git a/tests/OrmTest.php b/tests/OrmTest.php new file mode 100644 index 0000000..c79e15b --- /dev/null +++ b/tests/OrmTest.php @@ -0,0 +1,174 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2024 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * @link https://daniil.it/AsyncOrm AsyncOrm documentation + */ + +namespace danog\AsyncOrm\Test; + +use Amp\ByteStream\ReadableStream; +use Amp\Mysql\MysqlConfig; +use Amp\Postgres\PostgresConfig; +use Amp\Process\Process; +use Amp\Redis\RedisConfig; +use AssertionError; +use danog\AsyncOrm\FieldConfig; +use danog\AsyncOrm\KeyType; +use danog\AsyncOrm\Serializer\Igbinary; +use danog\AsyncOrm\Serializer\Json; +use danog\AsyncOrm\Serializer\Native; +use danog\AsyncOrm\Settings; +use danog\AsyncOrm\Settings\Mysql; +use danog\AsyncOrm\Settings\Postgres; +use danog\AsyncOrm\Settings\Redis; +use danog\AsyncOrm\ValueType; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +use function Amp\async; +use function Amp\ByteStream\buffer; +use function Amp\ByteStream\getStderr; +use function Amp\ByteStream\getStdout; +use function Amp\ByteStream\pipe; +use function Amp\ByteStream\splitLines; +use function Amp\Future\await; +use function Amp\Future\awaitAny; + +#[CoversNothing] +final class OrmTest extends TestCase +{ + /** @var array */ + private static array $processes = []; + private static function shellExec(string $cmd): void + { + $process = Process::start($cmd); + async(pipe(...), $process->getStderr(), getStderr()); + async(pipe(...), $process->getStdout(), getStdout()); + $process->join(); + } + public static function setUpBeforeClass(): void + { + $f = []; + foreach (['redis' => 6379, 'mariadb' => 3306, 'postgres' => 5432] as $image => $port) { + $f []= async(function () use ($image, $port) { + await([ + async(self::shellExec(...), "docker pull $image"), + async(self::shellExec(...), "docker rm -f test_$image 2>/dev/null") + ]); + + $args = match ($image) { + 'postgres' => '-e POSTGRES_HOST_AUTH_METHOD=trust', + 'mariadb' => '-e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1', + default => '' + }; + $process = Process::start( + "docker run --rm -p $port:$port $args --name test_$image $image" + ); + self::$processes[$image] = $process; + }); + } + await($f); + if (!self::$processes) { + throw new AssertionError("No processes!"); + } + foreach (self::$processes as $name => $process) { + $ok = awaitAny([ + async(self::waitForStartup(...), $process->getStdout()), + async(self::waitForStartup(...), $process->getStderr()), + ]); + if (!$ok) { + throw new AssertionError("Could not start $name!"); + } + } + } + private static function waitForStartup(ReadableStream $f): bool + { + foreach (splitLines($f) as $line) { + if (\stripos($line, 'ready to ') !== false + || \stripos($line, "socket: '/run/mysqld/mysqld.sock' port: 3306") !== false + ) { + async(buffer(...), $f); + return true; + } + } + return false; + } + public static function tearDownAfterClass(): void + { + foreach (self::$processes as $process) { + $process->signal(15); + } + } + + #[DataProvider('provideSettings')] + public function testAll(Settings $settings, KeyType $keyType, string|int $key): void + { + $field = new FieldConfig( + "test", + $settings, + $keyType, + ValueType::INT + ); + $orm = $field->build(); + $orm[$key] = 123; + + $this->assertEquals(123, $orm[$key]); + $this->assertTrue(isset($orm[$key])); + unset($orm[$key]); + + $this->assertNull($orm[$key]); + $this->assertFalse(isset($orm[$key])); + } + + public static function provideSettings(): \Generator + { + foreach ([new Native, new Igbinary, new Json] as $serializer) { + foreach ([ + new Redis( + RedisConfig::fromUri('redis://127.0.0.1'), + $serializer + ), + new Postgres( + PostgresConfig::fromString('host=127.0.0.1:5432 user=postgres db=test'), + $serializer + ), + new Mysql( + MysqlConfig::fromString('host=127.0.0.1:3306 user=root db=test'), + $serializer + ), + ] as $settings) { + yield [ + $settings, + KeyType::INT, + 123 + ]; + yield [ + $settings, + KeyType::STRING, + 'test' + ]; + yield [ + $settings, + KeyType::STRING_OR_INT, + 'test' + ]; + yield [ + $settings, + KeyType::STRING_OR_INT, + 'test' + ]; + } + } + } +}