From 1232aea45309cba2d91a6968472f858c6182901d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 1 Feb 2024 00:54:38 +0400 Subject: [PATCH 01/11] Add a test case --- composer.json | 2 +- .../Driver/Postgres/SerialColumnTest.php | 73 +++++++++++++++++++ tests/bootstrap.php | 13 +++- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php diff --git a/composer.json b/composer.json index bdedd2250..4f6085ced 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "funding": [ { "type": "github", - "url": "https://github.com/sponsors/cycle" + "url": "https://github.com/sponsors/cycle " } ], "require": { diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php new file mode 100644 index 000000000..7afd3346a --- /dev/null +++ b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php @@ -0,0 +1,73 @@ +getDatabase()->table('user')->getSchema(); + $schema->column('id')->type('uuid'); + $schema->column('balance')->type('serial')->nullable(false); + $schema->save(); + + $this->getDatabase()->table('user')->insertMultiple( + ['id'], + [ + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + ] + ); + + $this->orm = $this->withSchema(new Schema([ + User::class => [ + SchemaInterface::ROLE => 'user', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'user', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'balance'], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + ], + ])); + } + + public function testPersist(): void + { + $this->logger->display(); + $u = new User(); + $u->id = Uuid::uuid4()->toString(); + + $this->save($u); + + $this->assertNotNull($u->balance); + + $this->orm->getHeap()->clean(); + + $s = (new Select($this->orm, User::class))->wherePK($u->id)->fetchOne(); + + $this->assertSame($u->balance, $s->balance); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5ccbab2b9..ed6aaed0b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -25,7 +25,10 @@ user: 'root', password: 'root', ), - queryCache: true + queryCache: true, + options: [ + 'logQueryParameters' => true, + ], ), 'postgres' => new Config\PostgresDriverConfig( connection: new Config\Postgres\TcpConnectionConfig( @@ -37,6 +40,9 @@ ), schema: 'public', queryCache: true, + options: [ + 'logQueryParameters' => true, + ], ), 'sqlserver' => new Config\SQLServerDriverConfig( connection: new Config\SQLServer\TcpConnectionConfig( @@ -46,7 +52,10 @@ user: 'SA', password: 'SSpaSS__1' ), - queryCache: true + queryCache: true, + options: [ + 'logQueryParameters' => true, + ], ), ]; From f04ae1d95a89079e7f0bf971c449fb20956b3ec8 Mon Sep 17 00:00:00 2001 From: BelaRyc Date: Sun, 16 Apr 2023 16:40:08 +0300 Subject: [PATCH 02/11] tests for #321 --- .../Common/Integration/Case321/CaseTest.php | 61 +++++++++++++++++++ .../Integration/Case321/Entity/User1.php | 10 +++ .../Integration/Case321/Entity/User2.php | 10 +++ .../Common/Integration/Case321/schema.php | 48 +++++++++++++++ .../MySQL/Integration/Case321/CaseTest.php | 17 ++++++ .../Postgres/Integration/Case321/CaseTest.php | 17 ++++++ .../Integration/Case321/CaseTest.php | 17 ++++++ .../SQLite/Integration/Case321/CaseTest.php | 17 ++++++ 8 files changed, 197 insertions(+) create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User1.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User2.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php create mode 100644 tests/ORM/Functional/Driver/MySQL/Integration/Case321/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/Postgres/Integration/Case321/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/SQLServer/Integration/Case321/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/SQLite/Integration/Case321/CaseTest.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php new file mode 100644 index 000000000..110b98a32 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php @@ -0,0 +1,61 @@ +makeTables(); + + $this->loadSchema(__DIR__ . '/schema.php'); + } + + public function test1(): void + { + $user = new Entity\User1(); + + // Store changes and calc write queries + $this->captureWriteQueries(); + $this->save($user); + + // Check write queries count + $this->assertNumWrites(1); + } + + public function test2(): void + { + $user = new Entity\User2(); + + // Store changes and calc write queries + $this->captureWriteQueries(); + $this->save($user); + + // Check write queries count + $this->assertNumWrites(1); + } + + private function makeTables(): void + { + // Make tables + $this->makeTable('user1', [ + 'id' => 'primary', // autoincrement + ]); + + $this->makeTable('user2', [ + 'id' => 'primary', // autoincrement + ]); + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User1.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User1.php new file mode 100644 index 000000000..292e31c5f --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User1.php @@ -0,0 +1,10 @@ + [ + Schema::ENTITY => User1::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user1', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + ], + Schema::RELATIONS => [], + Schema::SCOPE => null, + Schema::TYPECAST => [ + 'id' => 'int', + ], + Schema::SCHEMA => [], + ], + 'user2' => [ + Schema::ENTITY => User2::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user2', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + ], + Schema::RELATIONS => [], + Schema::SCOPE => null, + Schema::TYPECAST => [ + 'id' => 'int', + ], + Schema::SCHEMA => [], + ], +]; diff --git a/tests/ORM/Functional/Driver/MySQL/Integration/Case321/CaseTest.php b/tests/ORM/Functional/Driver/MySQL/Integration/Case321/CaseTest.php new file mode 100644 index 000000000..7b3318f01 --- /dev/null +++ b/tests/ORM/Functional/Driver/MySQL/Integration/Case321/CaseTest.php @@ -0,0 +1,17 @@ + Date: Fri, 2 Feb 2024 23:12:04 +0400 Subject: [PATCH 03/11] Support generated fields in the Insert command --- src/Command/Database/Insert.php | 96 ++++++++++++++++--- src/Mapper/DatabaseMapper.php | 4 + src/SchemaInterface.php | 6 ++ .../Common/Integration/Case321/CaseTest.php | 1 - .../Common/Integration/Case321/schema.php | 6 ++ .../Driver/Postgres/SerialColumnTest.php | 3 + tests/ORM/Unit/Command/InsertCommandTest.php | 5 + 7 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index f35352f75..323fc9cfa 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -11,6 +11,7 @@ use Cycle\ORM\Command\Traits\MapperTrait; use Cycle\ORM\Heap\State; use Cycle\ORM\MapperInterface; +use Cycle\ORM\SchemaInterface; /** * Insert data into associated table and provide lastInsertID promise. @@ -20,14 +21,20 @@ final class Insert extends StoreCommand use ErrorTrait; use MapperTrait; + /** + * @param non-empty-string $table + * @param string[] $primaryKeys + * @param non-empty-string|null $pkColumn + * @param array $generated + */ public function __construct( DatabaseInterface $db, string $table, State $state, ?MapperInterface $mapper, - /** @var string[] */ private array $primaryKeys = [], - private ?string $pkColumn = null + private ?string $pkColumn = null, + private array $generated = [], ) { parent::__construct($db, $table, $state); $this->mapper = $mapper; @@ -40,7 +47,12 @@ public function isReady(): bool public function hasData(): bool { - return $this->columns !== [] || $this->state->getData() !== []; + return match (true) { + $this->columns !== [], + $this->state->getData() !== [], + $this->hasGeneratedFields() => true, + default => false, + }; } public function getStoreData(): array @@ -59,6 +71,7 @@ public function getStoreData(): array public function execute(): void { $state = $this->state; + $returningFields = []; if ($this->appendix !== []) { $state->setData($this->appendix); @@ -72,28 +85,65 @@ public function execute(): void unset($uncasted[$key]); } } + // unset db-generated fields if they are null + foreach ($this->generated as $column => $mode) { + if (($mode & SchemaInterface::GENERATED_DB) === 0x0) { + continue; + } + + $returningFields[$column] = $mode; + if (!isset($uncasted[$column])) { + unset($uncasted[$column]); + } + } $uncasted = $this->prepareData($uncasted); $insert = $this->db ->insert($this->table) ->values(\array_merge($this->columns, $uncasted)); - if ($this->pkColumn !== null && $insert instanceof ReturningInterface) { - $insert->returning($this->pkColumn); + if ($this->pkColumn !== null && $returningFields === []) { + $returningFields[$this->primaryKeys[0]] ??= $this->pkColumn; } - $insertID = $insert->run(); + if ($insert instanceof ReturningInterface && $returningFields !== []) { + // Map generated fields to columns + $returning = $this->mapper->mapColumns($returningFields); + // Array of [field name => column name] + $returning = \array_combine(\array_keys($returningFields), \array_keys($returning)); + + // TODO replace: + // $insert->returning(...\array_values($returning)); + $insert->returning(\array_values($returning)[0]); - if ($insertID !== null && \count($this->primaryKeys) === 1) { - $fpk = $this->primaryKeys[0]; // first PK - if (!isset($data[$fpk])) { + $insertID = $insert->run(); + + if (\count($returning) === 1) { + $field = \array_key_first($returning); $state->register( - $fpk, - $this->mapper === null ? $insertID : $this->mapper->cast([$fpk => $insertID])[$fpk] + $field, + $this->mapper === null ? $insertID : $this->mapper->cast([$field => $insertID])[$field], ); + } else { + foreach ($this->mapper->cast($insertID) as $field => $value) { + $state->register($field, $value); + } + } + } else { + $insertID = $insert->run(); + + if ($insertID !== null && \count($this->primaryKeys) === 1) { + $fpk = $this->primaryKeys[0]; // first PK + if (!isset($data[$fpk])) { + $state->register( + $fpk, + $this->mapper === null ? $insertID : $this->mapper->cast([$fpk => $insertID])[$fpk] + ); + } } } + $state->updateTransactionData(); parent::execute(); @@ -103,4 +153,28 @@ public function register(string $key, mixed $value): void { $this->state->register($key, $value); } + + /** + * Has fields that weren't provided but will be generated by the database or PHP. + */ + private function hasGeneratedFields(): bool + { + if ($this->generated === []) { + return false; + } + + $data = $this->state->getData(); + + foreach ($this->generated as $field => $mode) { + if (($mode & (SchemaInterface::GENERATED_DB | SchemaInterface::GENERATED_PHP_INSERT)) === 0x0) { + continue; + } + + if (!isset($data[$field])) { + return true; + } + } + + return true; + } } diff --git a/src/Mapper/DatabaseMapper.php b/src/Mapper/DatabaseMapper.php index e640aa6b6..167698e6e 100644 --- a/src/Mapper/DatabaseMapper.php +++ b/src/Mapper/DatabaseMapper.php @@ -39,6 +39,8 @@ abstract class DatabaseMapper implements MapperInterface protected array $primaryKeys; private ?TypecastInterface $typecast; protected RelationMap $relationMap; + /** @var array */ + private array $generatedFields; public function __construct( ORMInterface $orm, @@ -53,6 +55,7 @@ public function __construct( $this->columns[\is_int($property) ? $column : $property] = $column; } + $this->generatedFields = $schema->define($role, SchemaInterface::GENERATED_FIELDS) ?? []; // Parent's fields $parent = $schema->define($role, SchemaInterface::PARENT); while ($parent !== null) { @@ -128,6 +131,7 @@ public function queueCreate(object $entity, Node $node, State $state): CommandIn $this, $this->primaryKeys, \count($this->primaryColumns) === 1 ? $this->primaryColumns[0] : null, + $this->generatedFields, ); } diff --git a/src/SchemaInterface.php b/src/SchemaInterface.php index 835ab022e..4cf69a39c 100644 --- a/src/SchemaInterface.php +++ b/src/SchemaInterface.php @@ -31,6 +31,12 @@ interface SchemaInterface public const DISCRIMINATOR = 17; // Discriminator column name for single table inheritance public const LISTENERS = 18; public const TYPECAST_HANDLER = 19; // Typecast handler definition that implements TypecastInterface + public const GENERATED_FIELDS = 20; // List of generated fields [field => generating type] + + + public const GENERATED_DB = 1; // sequences and others + public const GENERATED_PHP_INSERT = 2; // generated by PHP code on insert like uuid + public const GENERATED_PHP_UPDATE = 4; // generated by PHP code on update like time /** * Return all roles defined within the schema. diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php index 110b98a32..dab22c655 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php @@ -4,7 +4,6 @@ namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case321; -use Cycle\ORM\Select; use Cycle\ORM\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Tests\Functional\Driver\Common\Integration\IntegrationTestTrait; use Cycle\ORM\Tests\Traits\TableTrait; diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php index e4862582c..a473fbf74 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php @@ -26,6 +26,9 @@ 'id' => 'int', ], Schema::SCHEMA => [], + Schema::GENERATED_FIELDS => [ + 'id' => Schema::GENERATED_DB, // autoincrement + ], ], 'user2' => [ Schema::ENTITY => User2::class, @@ -44,5 +47,8 @@ 'id' => 'int', ], Schema::SCHEMA => [], + Schema::GENERATED_FIELDS => [ + 'id' => Schema::GENERATED_DB, // autoincrement + ], ], ]; diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php index 7afd3346a..f5bad23ab 100644 --- a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php +++ b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php @@ -50,6 +50,9 @@ public function setUp(): void SchemaInterface::COLUMNS => ['id', 'balance'], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], + SchemaInterface::GENERATED_FIELDS => [ + 'balance' => SchemaInterface::GENERATED_DB, // sequence + ], ], ])); } diff --git a/tests/ORM/Unit/Command/InsertCommandTest.php b/tests/ORM/Unit/Command/InsertCommandTest.php index 266e5f63f..a29ffc1dd 100644 --- a/tests/ORM/Unit/Command/InsertCommandTest.php +++ b/tests/ORM/Unit/Command/InsertCommandTest.php @@ -142,6 +142,11 @@ public function testCommandWithReturningInterfaceShouldUseIt() ->with(['foo' => 'baaar']) ->andReturn(['baz' => 'bar']); + $this->mapper->shouldReceive('mapColumns') + ->once() + ->with(['id' => 'foo_id']) + ->andReturn(['foo_id' => 'foo_id']); + $this->mapper->shouldReceive('cast') ->once() ->with(['id' => 234]) From 6006ab609721bdfef3f058d3d02b55587594c790 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 6 Feb 2024 14:38:10 +0400 Subject: [PATCH 04/11] Test inserting an entity with many generated fields --- src/Command/Database/Insert.php | 4 +- src/Relation/ManyToMany.php | 2 - .../Driver/Postgres/SerialColumnTest.php | 50 ++++++++++++++++++- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index 323fc9cfa..4a35846b8 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -112,9 +112,7 @@ public function execute(): void // Array of [field name => column name] $returning = \array_combine(\array_keys($returningFields), \array_keys($returning)); - // TODO replace: - // $insert->returning(...\array_values($returning)); - $insert->returning(\array_values($returning)[0]); + $insert->returning(...\array_values($returning)); $insertID = $insert->run(); diff --git a/src/Relation/ManyToMany.php b/src/Relation/ManyToMany.php index 21d304092..2296ce0d9 100644 --- a/src/Relation/ManyToMany.php +++ b/src/Relation/ManyToMany.php @@ -363,8 +363,6 @@ protected function newLink(Pool $pool, Tuple $tuple, PivotedStorage $storage, ob foreach ($this->throughInnerKeys as $i => $pInnerKey) { $pTuple->state->register($pInnerKey, $tuple->state->getTransactionData()[$this->innerKeys[$i]] ?? null); - - // $rState->forward($this->outerKeys[$i], $pState, $this->throughOuterKeys[$i]); } if ($this->inversion === null) { diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php index f5bad23ab..eca5dc0c6 100644 --- a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php +++ b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php @@ -8,9 +8,11 @@ use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select; +use Cycle\ORM\Tests\Fixtures\CyclicRef2\Document; use Cycle\ORM\Tests\Fixtures\User; use Cycle\ORM\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Tests\Traits\TableTrait; +use DateTimeImmutable; use Ramsey\Uuid\Uuid; /** @@ -40,6 +42,13 @@ public function setUp(): void ] ); + $schema = $this->getDatabase()->table('document')->getSchema(); + $schema->column('id')->primary(); + $schema->column('body')->type('serial')->nullable(false); + $schema->column('created_at')->type('datetime')->nullable(false); + $schema->column('updated_at')->type('datetime')->nullable(false); + $schema->save(); + $this->orm = $this->withSchema(new Schema([ User::class => [ SchemaInterface::ROLE => 'user', @@ -54,12 +63,28 @@ public function setUp(): void 'balance' => SchemaInterface::GENERATED_DB, // sequence ], ], + Document::class => [ + SchemaInterface::ROLE => 'profile', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'document', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'body', 'created_at', 'updated_at'], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + SchemaInterface::GENERATED_FIELDS => [ + 'id' => SchemaInterface::GENERATED_DB, + 'body' => SchemaInterface::GENERATED_DB, + 'created_at' => SchemaInterface::GENERATED_PHP_INSERT, + 'updated_at' => SchemaInterface::GENERATED_PHP_INSERT | SchemaInterface::GENERATED_PHP_UPDATE, + ], + ], ])); + $this->logger->display(); } public function testPersist(): void { - $this->logger->display(); $u = new User(); $u->id = Uuid::uuid4()->toString(); @@ -73,4 +98,27 @@ public function testPersist(): void $this->assertSame($u->balance, $s->balance); } + + public function testPersistMultipleSerial(): void + { + $d1 = new Document(); + $d1->created_at = $d1->updated_at = new DateTimeImmutable(); + + $d2 = new Document(); + $d2->body = 213; + $d2->created_at = $d2->updated_at = new DateTimeImmutable(); + + $d3 = new Document(); + $d3->created_at = $d3->updated_at = new DateTimeImmutable(); + + + $this->save($d1, $d2, $d3); + + $this->assertSame(1, $d1->id); + $this->assertSame(1, $d1->body); + $this->assertSame(2, $d2->id); + $this->assertSame(213, $d2->body); + $this->assertSame(3, $d3->id); + $this->assertSame(2, $d3->body); + } } From ec0df10b7e9c9c25b6771b275017925c14fd08a2 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 7 Feb 2024 09:15:15 +0400 Subject: [PATCH 05/11] Update tst with many generated fields --- composer.json | 5 +-- .../ORM/Functional/Driver/Common/BaseTest.php | 8 +++- .../Driver/Postgres/SerialColumnTest.php | 39 ++++++++++++++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 4f6085ced..999eeef6c 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "funding": [ { "type": "github", - "url": "https://github.com/sponsors/cycle " + "url": "https://github.com/sponsors/cycle" } ], "require": { @@ -51,8 +51,7 @@ "phpunit/phpunit": "^9.5", "ramsey/uuid": "^4.0", "spiral/tokenizer": "^2.8 || ^3.0", - "vimeo/psalm": "5.21", - "buggregator/trap": "^1.4" + "vimeo/psalm": "5.21" }, "autoload": { "psr-4": { diff --git a/tests/ORM/Functional/Driver/Common/BaseTest.php b/tests/ORM/Functional/Driver/Common/BaseTest.php index ba446ae32..633f31329 100644 --- a/tests/ORM/Functional/Driver/Common/BaseTest.php +++ b/tests/ORM/Functional/Driver/Common/BaseTest.php @@ -97,7 +97,8 @@ public function setUp(): void null, new DoctrineCollectionFactory() ))->withCollectionFactory('array', new ArrayCollectionFactory()), - new Schema([]) + new Schema([]), + $this->getCommandGenerator(), ); } @@ -400,4 +401,9 @@ protected function applyDriverOptions(DriverConfig $config, array $options): voi $config->options['withDatetimeMicroseconds'] = $options['withDatetimeMicroseconds']; } } + + protected function getCommandGenerator(): ?Transaction\CommandGeneratorInterface + { + return null; + } } diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php index eca5dc0c6..85ba2fe7f 100644 --- a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php +++ b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php @@ -4,7 +4,10 @@ namespace Cycle\ORM\Tests\Functional\Driver\Postgres; +use Cycle\Database\Schema\AbstractColumn; +use Cycle\ORM\Command\CommandInterface; use Cycle\ORM\Mapper\Mapper; +use Cycle\ORM\ORMInterface; use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select; @@ -12,6 +15,7 @@ use Cycle\ORM\Tests\Fixtures\User; use Cycle\ORM\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Tests\Traits\TableTrait; +use Cycle\ORM\Transaction; use DateTimeImmutable; use Ramsey\Uuid\Uuid; @@ -45,7 +49,7 @@ public function setUp(): void $schema = $this->getDatabase()->table('document')->getSchema(); $schema->column('id')->primary(); $schema->column('body')->type('serial')->nullable(false); - $schema->column('created_at')->type('datetime')->nullable(false); + $schema->column('created_at')->type('datetime')->nullable(false)->defaultValue(AbstractColumn::DATETIME_NOW); $schema->column('updated_at')->type('datetime')->nullable(false); $schema->save(); @@ -72,10 +76,14 @@ public function setUp(): void SchemaInterface::COLUMNS => ['id', 'body', 'created_at', 'updated_at'], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], + SchemaInterface::TYPECAST => [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ], SchemaInterface::GENERATED_FIELDS => [ 'id' => SchemaInterface::GENERATED_DB, 'body' => SchemaInterface::GENERATED_DB, - 'created_at' => SchemaInterface::GENERATED_PHP_INSERT, + 'created_at' => SchemaInterface::GENERATED_DB, 'updated_at' => SchemaInterface::GENERATED_PHP_INSERT | SchemaInterface::GENERATED_PHP_UPDATE, ], ], @@ -102,23 +110,44 @@ public function testPersist(): void public function testPersistMultipleSerial(): void { $d1 = new Document(); - $d1->created_at = $d1->updated_at = new DateTimeImmutable(); $d2 = new Document(); $d2->body = 213; - $d2->created_at = $d2->updated_at = new DateTimeImmutable(); + $d2->created_at = $d2->updated_at = new DateTimeImmutable('2020-01-01'); $d3 = new Document(); - $d3->created_at = $d3->updated_at = new DateTimeImmutable(); + $d3->created_at = $d3->updated_at = new DateTimeImmutable('2020-01-01'); $this->save($d1, $d2, $d3); $this->assertSame(1, $d1->id); $this->assertSame(1, $d1->body); + $this->assertNotSame('2020-01-01', $d1->created_at->format('Y-m-d')); $this->assertSame(2, $d2->id); $this->assertSame(213, $d2->body); + $this->assertSame('2020-01-01', $d2->created_at->format('Y-m-d')); $this->assertSame(3, $d3->id); $this->assertSame(2, $d3->body); + $this->assertSame('2020-01-01', $d3->created_at->format('Y-m-d')); + } + + protected function getCommandGenerator(): ?Transaction\CommandGeneratorInterface + { + return new class extends Transaction\CommandGenerator { + protected function storeEntity(ORMInterface $orm, Transaction\Tuple $tuple, bool $isNew): ?CommandInterface + { + /** @var CommandInterface|null $command */ + $command = parent::storeEntity($orm, $tuple, $isNew); + + if ($command !== null && $tuple->entity instanceof Document && empty($tuple->entity->updated_at)) { + $now = new DateTimeImmutable(); + $tuple->state->register('updated_at', $now); + $tuple->entity->updated_at = $now; + } + + return $command; + } + }; } } From bd4433e64d92e243d133f0aa906370aece421157 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 7 Feb 2024 23:29:34 +0400 Subject: [PATCH 06/11] Add GeneratedField class; cleanup --- src/Command/Database/Insert.php | 5 ++- src/Heap/Traits/WaitFieldTrait.php | 2 +- src/Relation/BelongsTo.php | 2 +- src/Relation/RefersTo.php | 2 +- src/Schema/GeneratedField.php | 38 +++++++++++++++++++ src/SchemaInterface.php | 5 --- src/Transaction/Runner.php | 8 ++-- src/Transaction/UnitOfWork.php | 24 ++++++------ .../Common/Integration/Case321/schema.php | 5 ++- .../Driver/Postgres/SerialColumnTest.php | 13 ++++--- 10 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 src/Schema/GeneratedField.php diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index 4a35846b8..0518f041d 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -11,6 +11,7 @@ use Cycle\ORM\Command\Traits\MapperTrait; use Cycle\ORM\Heap\State; use Cycle\ORM\MapperInterface; +use Cycle\ORM\Schema\GeneratedField; use Cycle\ORM\SchemaInterface; /** @@ -87,7 +88,7 @@ public function execute(): void } // unset db-generated fields if they are null foreach ($this->generated as $column => $mode) { - if (($mode & SchemaInterface::GENERATED_DB) === 0x0) { + if (($mode & GeneratedField::DB_INSERT) === 0x0) { continue; } @@ -164,7 +165,7 @@ private function hasGeneratedFields(): bool $data = $this->state->getData(); foreach ($this->generated as $field => $mode) { - if (($mode & (SchemaInterface::GENERATED_DB | SchemaInterface::GENERATED_PHP_INSERT)) === 0x0) { + if (($mode & (GeneratedField::DB_INSERT | GeneratedField::PHP_INSERT)) === 0x0) { continue; } diff --git a/src/Heap/Traits/WaitFieldTrait.php b/src/Heap/Traits/WaitFieldTrait.php index 695b6be19..b985c1d92 100644 --- a/src/Heap/Traits/WaitFieldTrait.php +++ b/src/Heap/Traits/WaitFieldTrait.php @@ -18,7 +18,7 @@ public function waitField(string $key, bool $required = true): void public function getWaitingFields(bool $requiredOnly = false): array { - return array_keys($requiredOnly ? array_filter($this->waitingFields) : $this->waitingFields); + return \array_keys($requiredOnly ? \array_filter($this->waitingFields) : $this->waitingFields); } /** diff --git a/src/Relation/BelongsTo.php b/src/Relation/BelongsTo.php index 014071a54..0517ecc0b 100644 --- a/src/Relation/BelongsTo.php +++ b/src/Relation/BelongsTo.php @@ -179,7 +179,7 @@ private function checkNullValuePossibility(Tuple $tuple): bool } if ($tuple->status < Tuple::STATUS_PREPROCESSED - && array_intersect($this->innerKeys, $tuple->state->getWaitingFields(false)) !== [] + && \array_intersect($this->innerKeys, $tuple->state->getWaitingFields(false)) !== [] ) { return true; } diff --git a/src/Relation/RefersTo.php b/src/Relation/RefersTo.php index a150fdee8..9858cd806 100644 --- a/src/Relation/RefersTo.php +++ b/src/Relation/RefersTo.php @@ -98,7 +98,7 @@ public function queue(Pool $pool, Tuple $tuple): void if ($rTuple->status === Tuple::STATUS_PROCESSED || ($rTuple->status > Tuple::STATUS_PREPARING && $rTuple->state->getStatus() !== node::NEW - && array_intersect($this->outerKeys, $rTuple->state->getWaitingFields()) === []) + && \array_intersect($this->outerKeys, $rTuple->state->getWaitingFields()) === []) ) { $this->pullValues($tuple->state, $rTuple->state); $node->setRelation($this->getName(), $related); diff --git a/src/Schema/GeneratedField.php b/src/Schema/GeneratedField.php new file mode 100644 index 000000000..f2bd7e28e --- /dev/null +++ b/src/Schema/GeneratedField.php @@ -0,0 +1,38 @@ + generating type] - - public const GENERATED_DB = 1; // sequences and others - public const GENERATED_PHP_INSERT = 2; // generated by PHP code on insert like uuid - public const GENERATED_PHP_UPDATE = 4; // generated by PHP code on update like time - /** * Return all roles defined within the schema. */ diff --git a/src/Transaction/Runner.php b/src/Transaction/Runner.php index 224396df8..2083f15ec 100644 --- a/src/Transaction/Runner.php +++ b/src/Transaction/Runner.php @@ -75,7 +75,7 @@ public function complete(): void { if ($this->mode === self::MODE_OPEN_TRANSACTION) { // Commit all of the open and normalized database transactions - foreach (array_reverse($this->drivers) as $driver) { + foreach (\array_reverse($this->drivers) as $driver) { /** @var DriverInterface $driver */ $driver->commitTransaction(); } @@ -94,14 +94,14 @@ public function rollback(): void { if ($this->mode === self::MODE_OPEN_TRANSACTION) { // Close all open and normalized database transactions - foreach (array_reverse($this->drivers) as $driver) { + foreach (\array_reverse($this->drivers) as $driver) { /** @var DriverInterface $driver */ $driver->rollbackTransaction(); } } // Close all of external types of transactions (revert changes) - foreach (array_reverse($this->executed) as $command) { + foreach (\array_reverse($this->executed) as $command) { if ($command instanceof RollbackMethodInterface) { $command->rollBack(); } @@ -145,7 +145,7 @@ private function prepareTransaction(DriverInterface $driver): void if ($this->mode === self::MODE_CONTINUE_TRANSACTION) { if ($driver->getTransactionLevel() === 0) { - throw new RunnerException(sprintf( + throw new RunnerException(\sprintf( 'The `%s` driver connection has no opened transaction.', $driver->getType() )); diff --git a/src/Transaction/UnitOfWork.php b/src/Transaction/UnitOfWork.php index 9ebbab7f3..2582392e8 100644 --- a/src/Transaction/UnitOfWork.php +++ b/src/Transaction/UnitOfWork.php @@ -163,7 +163,7 @@ private function syncHeap(): void $node = $tuple->node; // marked as being deleted and has no external claims (GC like approach) - if (in_array($node->getStatus(), [Node::DELETED, Node::SCHEDULED_DELETE], true)) { + if (\in_array($node->getStatus(), [Node::DELETED, Node::SCHEDULED_DELETE], true)) { $heap->detach($e); continue; } @@ -174,7 +174,7 @@ private function syncHeap(): void $heap->attach($e, $node, $indexProvider->getIndexes($role)); if ($tuple->persist !== null) { - $syncData = array_udiff_assoc( + $syncData = \array_udiff_assoc( $tuple->state->getData(), $tuple->persist->getData(), [Node::class, 'compare'] @@ -265,7 +265,7 @@ private function resolveSlaveRelations(Tuple $tuple, RelationMap $map): int if (!$map->hasSlaves()) { return self::RELATIONS_RESOLVED; } - $changedFields = array_keys($tuple->state->getChanges()); + $changedFields = \array_keys($tuple->state->getChanges()); // Attach children to pool $transactData = $tuple->state->getTransactionData(); @@ -281,15 +281,15 @@ private function resolveSlaveRelations(Tuple $tuple, RelationMap $map): int } $innerKeys = $relation->getInnerKeys(); - $isWaitingKeys = array_intersect($innerKeys, $tuple->state->getWaitingFields(true)) !== []; - $hasChangedKeys = array_intersect($innerKeys, $changedFields) !== []; + $isWaitingKeys = \array_intersect($innerKeys, $tuple->state->getWaitingFields(true)) !== []; + $hasChangedKeys = \array_intersect($innerKeys, $changedFields) !== []; if ($relationStatus === RelationInterface::STATUS_PREPARE) { $relData ??= $tuple->mapper->fetchRelations($tuple->entity); $relation->prepare( $this->pool, $tuple, $relData[$name] ?? null, - $isWaitingKeys || $hasChangedKeys + $isWaitingKeys || $hasChangedKeys, ); $relationStatus = $tuple->state->getRelationStatus($relation->getName()); } @@ -298,9 +298,9 @@ private function resolveSlaveRelations(Tuple $tuple, RelationMap $map): int && $relationStatus !== RelationInterface::STATUS_RESOLVED && !$isWaitingKeys && !$hasChangedKeys - && \count(array_intersect($innerKeys, array_keys($transactData))) === \count($innerKeys) + && \count(\array_intersect($innerKeys, \array_keys($transactData))) === \count($innerKeys) ) { - $child ??= $tuple->state->getRelation($name); + // $child ??= $tuple->state->getRelation($name); $relation->queue($this->pool, $tuple); $relationStatus = $tuple->state->getRelationStatus($relation->getName()); } @@ -316,7 +316,7 @@ private function resolveSelfWithEmbedded(Tuple $tuple, RelationMap $map, bool $h if (!$map->hasEmbedded() && !$tuple->state->hasChanges()) { $tuple->status = !$hasDeferredRelations ? Tuple::STATUS_PROCESSED - : max(Tuple::STATUS_DEFERRED, $tuple->status); + : \max(Tuple::STATUS_DEFERRED, $tuple->status); return; } @@ -351,14 +351,13 @@ private function resolveSelfWithEmbedded(Tuple $tuple, RelationMap $map, bool $h $tuple->status = $tuple->status === Tuple::STATUS_PREPROCESSED || !$hasDeferredRelations ? Tuple::STATUS_PROCESSED - : max(Tuple::STATUS_DEFERRED, $tuple->status); + : \max(Tuple::STATUS_DEFERRED, $tuple->status); } private function resolveRelations(Tuple $tuple): void { $map = $this->orm->getRelationMap($tuple->node->getRole()); - // Dependency relations $result = $tuple->task === Tuple::TASK_STORE ? $this->resolveMasterRelations($tuple, $map) : $this->resolveSlaveRelations($tuple, $map); @@ -368,8 +367,8 @@ private function resolveRelations(Tuple $tuple): void // Self if ($deferred && $tuple->status < Tuple::STATUS_PROPOSED) { $tuple->status = Tuple::STATUS_DEFERRED; - // $this->pool->attachTuple($tuple); } + if ($isDependenciesResolved) { if ($tuple->task === Tuple::TASK_STORE) { $this->resolveSelfWithEmbedded($tuple, $map, $deferred); @@ -383,7 +382,6 @@ private function resolveRelations(Tuple $tuple): void } if ($tuple->cascade) { - // Slave relations relations $tuple->task === Tuple::TASK_STORE ? $this->resolveSlaveRelations($tuple, $map) : $this->resolveMasterRelations($tuple, $map); diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php index a473fbf74..98a8c8008 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Cycle\ORM\Mapper\Mapper; +use Cycle\ORM\Schema\GeneratedField; use Cycle\ORM\SchemaInterface as Schema; use Cycle\ORM\Select\Source; use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case321\Entity\User1; @@ -27,7 +28,7 @@ ], Schema::SCHEMA => [], Schema::GENERATED_FIELDS => [ - 'id' => Schema::GENERATED_DB, // autoincrement + 'id' => GeneratedField::DB_INSERT, // autoincrement ], ], 'user2' => [ @@ -48,7 +49,7 @@ ], Schema::SCHEMA => [], Schema::GENERATED_FIELDS => [ - 'id' => Schema::GENERATED_DB, // autoincrement + 'id' => GeneratedField::DB_INSERT, // autoincrement ], ], ]; diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php index 85ba2fe7f..8ecac5819 100644 --- a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php +++ b/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php @@ -9,6 +9,7 @@ use Cycle\ORM\Mapper\Mapper; use Cycle\ORM\ORMInterface; use Cycle\ORM\Schema; +use Cycle\ORM\Schema\GeneratedField; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select; use Cycle\ORM\Tests\Fixtures\CyclicRef2\Document; @@ -64,7 +65,7 @@ public function setUp(): void SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::GENERATED_FIELDS => [ - 'balance' => SchemaInterface::GENERATED_DB, // sequence + 'balance' => GeneratedField::DB_INSERT, // sequence ], ], Document::class => [ @@ -81,10 +82,10 @@ public function setUp(): void 'updated_at' => 'datetime', ], SchemaInterface::GENERATED_FIELDS => [ - 'id' => SchemaInterface::GENERATED_DB, - 'body' => SchemaInterface::GENERATED_DB, - 'created_at' => SchemaInterface::GENERATED_DB, - 'updated_at' => SchemaInterface::GENERATED_PHP_INSERT | SchemaInterface::GENERATED_PHP_UPDATE, + 'id' => GeneratedField::DB_INSERT, + 'body' => GeneratedField::DB_INSERT, + 'created_at' => GeneratedField::DB_INSERT, + 'updated_at' => GeneratedField::PHP_INSERT | GeneratedField::PHP_UPDATE, ], ], ])); @@ -134,7 +135,7 @@ public function testPersistMultipleSerial(): void protected function getCommandGenerator(): ?Transaction\CommandGeneratorInterface { - return new class extends Transaction\CommandGenerator { + return new class () extends Transaction\CommandGenerator { protected function storeEntity(ORMInterface $orm, Transaction\Tuple $tuple, bool $isNew): ?CommandInterface { /** @var CommandInterface|null $command */ From eec5a7d45b3b5e417d688ae2b73bd728024e19c3 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 8 Feb 2024 15:53:29 +0400 Subject: [PATCH 07/11] Set the required cycle/database version --- composer.json | 2 +- src/Command/Database/Insert.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 999eeef6c..e7ab4eea2 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "cycle/database": "^2.6", + "cycle/database": "^2.8", "doctrine/instantiator": "^1.3.1 || ^2.0", "spiral/core": "^2.8 || ^3.0" }, diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index 0518f041d..47b21e0fd 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -12,7 +12,6 @@ use Cycle\ORM\Heap\State; use Cycle\ORM\MapperInterface; use Cycle\ORM\Schema\GeneratedField; -use Cycle\ORM\SchemaInterface; /** * Insert data into associated table and provide lastInsertID promise. From 4cce548dc024e5f6239001fd0e3af843f242d6d6 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 8 Feb 2024 18:31:58 +0400 Subject: [PATCH 08/11] Rename the new constants; add test for MSSQL --- src/Command/Database/Insert.php | 4 +- src/Schema/GeneratedField.php | 16 ++--- .../GeneratedColumnTest.php} | 58 ++++++------------- .../Common/Integration/Case321/schema.php | 4 +- .../Embedded/EmbeddedRelationTest.php | 3 +- .../Driver/Common/Typecast/TypecastTest.php | 2 +- .../Driver/Postgres/GeneratedColumnTest.php | 43 ++++++++++++++ .../Driver/SQLServer/GeneratedColumnTest.php | 52 +++++++++++++++++ tests/bootstrap.php | 6 +- 9 files changed, 130 insertions(+), 58 deletions(-) rename tests/ORM/Functional/Driver/{Postgres/SerialColumnTest.php => Common/GeneratedColumnTest.php} (68%) create mode 100644 tests/ORM/Functional/Driver/Postgres/GeneratedColumnTest.php create mode 100644 tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index 47b21e0fd..53a1812e9 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -87,7 +87,7 @@ public function execute(): void } // unset db-generated fields if they are null foreach ($this->generated as $column => $mode) { - if (($mode & GeneratedField::DB_INSERT) === 0x0) { + if (($mode & GeneratedField::ON_INSERT) === 0x0) { continue; } @@ -164,7 +164,7 @@ private function hasGeneratedFields(): bool $data = $this->state->getData(); foreach ($this->generated as $field => $mode) { - if (($mode & (GeneratedField::DB_INSERT | GeneratedField::PHP_INSERT)) === 0x0) { + if (($mode & (GeneratedField::ON_INSERT | GeneratedField::BEFORE_INSERT)) === 0x0) { continue; } diff --git a/src/Schema/GeneratedField.php b/src/Schema/GeneratedField.php index f2bd7e28e..26eee49b2 100644 --- a/src/Schema/GeneratedField.php +++ b/src/Schema/GeneratedField.php @@ -14,12 +14,6 @@ final class GeneratedField */ public const DEFAULT = 0; - /** - * Field value is generated by the Database. - * It is: autoincrement fields, sequences, timestamps, etc. - */ - public const DB_INSERT = 1; - /** * Field value is generated by PHP code before Insert command running * like with `CreatedAt` or `Uuid` Entity Behaviors @@ -27,12 +21,18 @@ final class GeneratedField * @link https://github.com/cycle/entity-behavior * @link https://github.com/cycle/entity-behavior-uuid */ - public const PHP_INSERT = 2; + public const BEFORE_INSERT = 1; + + /** + * Field value is generated by the Database. + * It is: autoincrement fields, sequences, timestamps, etc. + */ + public const ON_INSERT = 2; /** * Field value is generated by PHP code before Update command running like with `UpdatedAt` Entity Behavior. * * @link https://github.com/cycle/entity-behavior */ - public const PHP_UPDATE = 4; + public const BEFORE_UPDATE = 4; } diff --git a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php b/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php similarity index 68% rename from tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php rename to tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php index 8ecac5819..9ddf7629a 100644 --- a/tests/ORM/Functional/Driver/Postgres/SerialColumnTest.php +++ b/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Postgres; +namespace Cycle\ORM\Tests\Functional\Driver\Common; -use Cycle\Database\Schema\AbstractColumn; use Cycle\ORM\Command\CommandInterface; use Cycle\ORM\Mapper\Mapper; use Cycle\ORM\ORMInterface; @@ -14,45 +13,22 @@ use Cycle\ORM\Select; use Cycle\ORM\Tests\Fixtures\CyclicRef2\Document; use Cycle\ORM\Tests\Fixtures\User; -use Cycle\ORM\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Tests\Traits\TableTrait; +use Cycle\ORM\Tests\Util\DontGenerateAttribute; use Cycle\ORM\Transaction; use DateTimeImmutable; use Ramsey\Uuid\Uuid; -/** - * @group driver - * @group driver-postgres - */ -final class SerialColumnTest extends BaseTest +#[DontGenerateAttribute] +abstract class GeneratedColumnTest extends BaseTest { - public const DRIVER = 'postgres'; use TableTrait; public function setUp(): void { parent::setUp(); - $schema = $this->getDatabase()->table('user')->getSchema(); - $schema->column('id')->type('uuid'); - $schema->column('balance')->type('serial')->nullable(false); - $schema->save(); - - $this->getDatabase()->table('user')->insertMultiple( - ['id'], - [ - [Uuid::uuid4()->toString()], - [Uuid::uuid4()->toString()], - [Uuid::uuid4()->toString()], - ] - ); - - $schema = $this->getDatabase()->table('document')->getSchema(); - $schema->column('id')->primary(); - $schema->column('body')->type('serial')->nullable(false); - $schema->column('created_at')->type('datetime')->nullable(false)->defaultValue(AbstractColumn::DATETIME_NOW); - $schema->column('updated_at')->type('datetime')->nullable(false); - $schema->save(); + $this->createTables(); $this->orm = $this->withSchema(new Schema([ User::class => [ @@ -65,7 +41,7 @@ public function setUp(): void SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::GENERATED_FIELDS => [ - 'balance' => GeneratedField::DB_INSERT, // sequence + 'balance' => GeneratedField::ON_INSERT, // sequence ], ], Document::class => [ @@ -82,10 +58,10 @@ public function setUp(): void 'updated_at' => 'datetime', ], SchemaInterface::GENERATED_FIELDS => [ - 'id' => GeneratedField::DB_INSERT, - 'body' => GeneratedField::DB_INSERT, - 'created_at' => GeneratedField::DB_INSERT, - 'updated_at' => GeneratedField::PHP_INSERT | GeneratedField::PHP_UPDATE, + 'id' => GeneratedField::ON_INSERT, + 'body' => GeneratedField::ON_INSERT, + 'created_at' => GeneratedField::ON_INSERT, + 'updated_at' => GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, ], ], ])); @@ -122,14 +98,14 @@ public function testPersistMultipleSerial(): void $this->save($d1, $d2, $d3); - $this->assertSame(1, $d1->id); - $this->assertSame(1, $d1->body); + $this->assertEquals(1, $d1->id); + $this->assertEquals(1, $d1->body); $this->assertNotSame('2020-01-01', $d1->created_at->format('Y-m-d')); - $this->assertSame(2, $d2->id); - $this->assertSame(213, $d2->body); + $this->assertEquals(2, $d2->id); + $this->assertEquals(213, $d2->body); $this->assertSame('2020-01-01', $d2->created_at->format('Y-m-d')); - $this->assertSame(3, $d3->id); - $this->assertSame(2, $d3->body); + $this->assertEquals(3, $d3->id); + $this->assertEquals(2, $d3->body); $this->assertSame('2020-01-01', $d3->created_at->format('Y-m-d')); } @@ -151,4 +127,6 @@ protected function storeEntity(ORMInterface $orm, Transaction\Tuple $tuple, bool } }; } + + abstract function createTables(): void; } diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php index 98a8c8008..df203a8f8 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/schema.php @@ -28,7 +28,7 @@ ], Schema::SCHEMA => [], Schema::GENERATED_FIELDS => [ - 'id' => GeneratedField::DB_INSERT, // autoincrement + 'id' => GeneratedField::ON_INSERT, // autoincrement ], ], 'user2' => [ @@ -49,7 +49,7 @@ ], Schema::SCHEMA => [], Schema::GENERATED_FIELDS => [ - 'id' => GeneratedField::DB_INSERT, // autoincrement + 'id' => GeneratedField::ON_INSERT, // autoincrement ], ], ]; diff --git a/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php b/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php index 87014cfb6..4e8efbab8 100644 --- a/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php +++ b/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php @@ -128,6 +128,7 @@ public function testInitRelationFetchOne(): void public function testCreateUserWithEmbedded(): void { + $this->logger->display(); $u = new User(); $u->email = 'new@email.com'; $u->balance = 900; @@ -138,7 +139,7 @@ public function testCreateUserWithEmbedded(): void $this->save($u); $this->assertNumWrites(1); - $this->assertSame(3, $u->id); + $this->assertSame(3, (int)$u->id); $selector = new Select($this->orm->withHeap(new Heap()), User::class); $u2 = $selector->load('credentials')->wherePK($u->id)->fetchOne(); diff --git a/tests/ORM/Functional/Driver/Common/Typecast/TypecastTest.php b/tests/ORM/Functional/Driver/Common/Typecast/TypecastTest.php index f34d14787..9051a1462 100644 --- a/tests/ORM/Functional/Driver/Common/Typecast/TypecastTest.php +++ b/tests/ORM/Functional/Driver/Common/Typecast/TypecastTest.php @@ -143,7 +143,7 @@ public function testAIIdTypecastingOnInsert(): void $this->save($user); $this->assertNotNull($user->id); - $this->assertIsInt($user->id->value); + $this->assertIsNumeric($user->id->value); } // ORM::make() diff --git a/tests/ORM/Functional/Driver/Postgres/GeneratedColumnTest.php b/tests/ORM/Functional/Driver/Postgres/GeneratedColumnTest.php new file mode 100644 index 000000000..770264b59 --- /dev/null +++ b/tests/ORM/Functional/Driver/Postgres/GeneratedColumnTest.php @@ -0,0 +1,43 @@ +getDatabase()->table('user')->getSchema(); + $schema->column('id')->type('uuid'); + $schema->column('balance')->type('serial')->nullable(false); + $schema->save(); + + $this->getDatabase()->table('user')->insertMultiple( + ['id'], + [ + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + ] + ); + + $schema = $this->getDatabase()->table('document')->getSchema(); + $schema->column('id')->primary(); + $schema->column('body')->type('serial')->nullable(false); + $schema->column('created_at')->type('datetime')->nullable(false)->defaultValue(AbstractColumn::DATETIME_NOW); + $schema->column('updated_at')->type('datetime')->nullable(false); + $schema->save(); + } +} diff --git a/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php b/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php new file mode 100644 index 000000000..ece4004c6 --- /dev/null +++ b/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php @@ -0,0 +1,52 @@ +logger->display(); + $this->getDatabase()->query('DROP SEQUENCE IF EXISTS testSequence1;'); + $this->getDatabase()->query('DROP SEQUENCE IF EXISTS testSequence2;'); + $this->getDatabase()->query('CREATE SEQUENCE testSequence1 START WITH 1;'); + $this->getDatabase()->query('CREATE SEQUENCE testSequence2 START WITH 1;'); + + $schema = $this->getDatabase()->table('user')->getSchema(); + $schema->column('id')->type('uuid'); + $schema->column('balance')->type('int')->nullable(false) + ->defaultValue(new Fragment('NEXT VALUE FOR testSequence1')); + $schema->save(); + + $this->getDatabase()->table('user')->insertMultiple( + ['id'], + [ + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + [Uuid::uuid4()->toString()], + ] + ); + + $schema = $this->getDatabase()->table('document')->getSchema(); + $schema->column('id')->primary(); + $schema->column('body')->type('int')->nullable(false) + ->defaultValue(new Fragment('NEXT VALUE FOR testSequence2')); + $schema->column('created_at')->type('datetime')->nullable(false)->defaultValue(AbstractColumn::DATETIME_NOW); + $schema->column('updated_at')->type('datetime')->nullable(false); + $schema->save(); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ed6aaed0b..e6f3f0030 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -45,10 +45,8 @@ ], ), 'sqlserver' => new Config\SQLServerDriverConfig( - connection: new Config\SQLServer\TcpConnectionConfig( - database: 'tempdb', - host: '127.0.0.1', - port: 11433, + connection: new Config\SQLServer\DsnConnectionConfig( + 'sqlsrv:Server=127.0.0.1,11433;Database=tempdb;TrustServerCertificate=true', user: 'SA', password: 'SSpaSS__1' ), From 3e99cb5d743f53bb56a9b4db5492016f16402371 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 8 Feb 2024 20:08:51 +0400 Subject: [PATCH 09/11] cleanup --- composer.json | 2 +- tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php | 3 +-- .../Driver/Common/Relation/Embedded/EmbeddedRelationTest.php | 1 - tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index e7ab4eea2..eae93bb4d 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "cycle/database": "^2.8", + "cycle/database": "^2.8.1", "doctrine/instantiator": "^1.3.1 || ^2.0", "spiral/core": "^2.8 || ^3.0" }, diff --git a/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php b/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php index 9ddf7629a..8f8778978 100644 --- a/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php +++ b/tests/ORM/Functional/Driver/Common/GeneratedColumnTest.php @@ -65,7 +65,6 @@ public function setUp(): void ], ], ])); - $this->logger->display(); } public function testPersist(): void @@ -128,5 +127,5 @@ protected function storeEntity(ORMInterface $orm, Transaction\Tuple $tuple, bool }; } - abstract function createTables(): void; + abstract public function createTables(): void; } diff --git a/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php b/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php index 4e8efbab8..e02efd44a 100644 --- a/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php +++ b/tests/ORM/Functional/Driver/Common/Relation/Embedded/EmbeddedRelationTest.php @@ -128,7 +128,6 @@ public function testInitRelationFetchOne(): void public function testCreateUserWithEmbedded(): void { - $this->logger->display(); $u = new User(); $u->email = 'new@email.com'; $u->balance = 900; diff --git a/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php b/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php index ece4004c6..30e6f0924 100644 --- a/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php +++ b/tests/ORM/Functional/Driver/SQLServer/GeneratedColumnTest.php @@ -20,7 +20,6 @@ class GeneratedColumnTest extends CommonClass public function createTables(): void { - $this->logger->display(); $this->getDatabase()->query('DROP SEQUENCE IF EXISTS testSequence1;'); $this->getDatabase()->query('DROP SEQUENCE IF EXISTS testSequence2;'); $this->getDatabase()->query('CREATE SEQUENCE testSequence1 START WITH 1;'); From 21898ccb386b8d8e2de56e799283b50bc85ea496 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 8 Feb 2024 21:34:16 +0400 Subject: [PATCH 10/11] Add more tests --- src/Command/Database/Insert.php | 3 +- src/Heap/State.php | 4 +- .../Common/Integration/Case321/CaseTest.php | 33 +++++++++++++++ .../Integration/Case321/Entity/User3.php | 10 +++++ .../Integration/Case321/Entity/User4.php | 11 +++++ .../Common/Integration/Case321/schema.php | 40 +++++++++++++++++++ 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User3.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User4.php diff --git a/src/Command/Database/Insert.php b/src/Command/Database/Insert.php index 53a1812e9..2e3fe6b55 100644 --- a/src/Command/Database/Insert.php +++ b/src/Command/Database/Insert.php @@ -141,7 +141,6 @@ public function execute(): void } } - $state->updateTransactionData(); parent::execute(); @@ -173,6 +172,6 @@ private function hasGeneratedFields(): bool } } - return true; + return false; } } diff --git a/src/Heap/State.php b/src/Heap/State.php index 743da9938..05338e6c4 100644 --- a/src/Heap/State.php +++ b/src/Heap/State.php @@ -163,7 +163,7 @@ public function getChanges(): array public function getValue(string $key): mixed { - return array_key_exists($key, $this->data) ? $this->data[$key] : ($this->transactionData[$key] ?? null); + return \array_key_exists($key, $this->data) ? $this->data[$key] : ($this->transactionData[$key] ?? null); } public function hasValue(string $key, bool $allowNull = true): bool @@ -171,7 +171,7 @@ public function hasValue(string $key, bool $allowNull = true): bool if (!$allowNull) { return isset($this->data[$key]) || isset($this->transactionData[$key]); } - return array_key_exists($key, $this->data) || array_key_exists($key, $this->transactionData); + return \array_key_exists($key, $this->data) || \array_key_exists($key, $this->transactionData); } public function register(string $key, mixed $value): void diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php index dab22c655..9f72f6722 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php @@ -46,6 +46,29 @@ public function test2(): void $this->assertNumWrites(1); } + public function test3(): void + { + $user = new Entity\User3(); + + $this->captureWriteQueries(); + $this->save($user); + + // ORM won't detect any values to store + // because the id field isn't marked as autogenerated + $this->assertNumWrites(0); + } + + public function test4(): void + { + $user = new Entity\User4(); + + $this->save($user); + + // ORM won't detect any values to store + // because the id field isn't marked as autogenerated + $this->assertNumWrites(0); + } + private function makeTables(): void { // Make tables @@ -56,5 +79,15 @@ private function makeTables(): void $this->makeTable('user2', [ 'id' => 'primary', // autoincrement ]); + + $this->makeTable('user3', [ + 'id' => 'primary', // autoincrement + ]); + + $this->logger->display(); + $this->makeTable('user4', [ + 'id' => 'primary', + 'counter' => 'int', + ]); } } diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User3.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User3.php new file mode 100644 index 000000000..a064d5890 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/Entity/User3.php @@ -0,0 +1,10 @@ + [ @@ -52,4 +54,42 @@ 'id' => GeneratedField::ON_INSERT, // autoincrement ], ], + 'user3' => [ + Schema::ENTITY => User3::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user3', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + ], + Schema::RELATIONS => [], + Schema::SCOPE => null, + Schema::TYPECAST => [ + 'id' => 'int', + ], + Schema::SCHEMA => [], + Schema::GENERATED_FIELDS => [], + ], + 'user4' => [ + Schema::ENTITY => User4::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user4', + Schema::PRIMARY_KEY => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + 'counter' => 'counter', + ], + Schema::TYPECAST => [ + 'id' => 'int', + 'counter' => 'counter', + ], + Schema::GENERATED_FIELDS => [ + 'counter' => GeneratedField::BEFORE_UPDATE, + ], + ], ]; From 2e662dd13f75a3c920974409d098584ad354e27e Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 8 Feb 2024 21:51:17 +0400 Subject: [PATCH 11/11] Update changelog --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b37d88f4..6ef221ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # CHANGELOG -v2.6.0 (22.12.2023) +v2.7.0 (08.02.2024) +-------------------- +- Add Generated Fields option into ORM Schema by @roxblnfk (#462) + +v2.6.1 (04.01.2024) +-------------------- +- Fix compatibility with PHP 8.3 by @msmakouz (#454) + +- v2.6.0 (22.12.2023) -------------------- - Add support for `loophp/collection` v7 by @msmakouz (#448) - Fix wrong adding table prefix on joins by @msmakouz (#447)