From 532f948eba9b414d0b701d2bf12e11e94abf377c Mon Sep 17 00:00:00 2001 From: gam6itko Date: Fri, 25 Nov 2022 16:36:21 +0300 Subject: [PATCH 01/10] [tests] broken insert order --- .../Common/Integration/Case4/CaseTest.php | 166 ++++++++++++++++ .../Common/Integration/Case4/Entity/User.php | 19 ++ .../Integration/Case4/Entity/User/Alias.php | 22 +++ .../Integration/Case4/Entity/User/Email.php | 22 +++ .../Integration/Case4/Entity/User/Phone.php | 22 +++ .../Common/Integration/Case4/schema.php | 178 ++++++++++++++++++ .../MySQL/Integration/Case4/CaseTest.php | 17 ++ .../Postgres/Integration/Case4/CaseTest.php | 17 ++ .../SQLServer/Integration/Case4/CaseTest.php | 17 ++ .../SQLite/Integration/Case4/CaseTest.php | 17 ++ 10 files changed, 497 insertions(+) create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php create mode 100644 tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php create mode 100644 tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/Postgres/Integration/Case4/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/SQLServer/Integration/Case4/CaseTest.php create mode 100644 tests/ORM/Functional/Driver/SQLite/Integration/Case4/CaseTest.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php new file mode 100644 index 00000000..65a56218 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php @@ -0,0 +1,166 @@ +makeTables(); + $this->fillData(); + + $this->loadSchema(__DIR__.'/schema.php'); + } + + public function testOnce(): void + { + /** @var User $user */ + $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); + + $em = (new EntityManager($this->orm)); + $em->persist($user); + + $i = 0; + + $em->persist(new User\Alias($user, (string) ++$i)); + $em->persist(new User\Alias($user, (string) ++$i)); + + $em->persist(new User\Email($user, (string) ++$i)); + $em->persist(new User\Email($user, (string) ++$i)); + + $em->persist(new User\Phone($user, (string) ++$i)); + $em->persist(new User\Phone($user, (string) ++$i)); + + $em->run(); + + $db = $this->orm->getSource(User::class)->getDatabase(); + self::assertSame( + [ + 'alias' => [ + ['value' => '1'], + ['value' => '2'], + ], + 'email' => [ + ['value' => '3'], + ['value' => '4'], + ], + 'phone' => [ + ['value' => '5'], + ['value' => '6'], + ], + ], + [ + 'alias' => $db->select('value')->from('user_alias')->fetchAll(), + 'email' => $db->select('value')->from('user_email')->fetchAll(), + 'phone' => $db->select('value')->from('user_phone')->fetchAll(), + ] + ); + } + + /** + * @dataProvider dataMatrix + */ + public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void + { + /** @var User $user */ + $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); + + $em = (new EntityManager($this->orm)); + $em->persist($user); + + $i = 0; + + $expected = []; + while ($cnt1-- > 0) { + $em->persist(new User\Alias($user, $v = (string) ++$i)); + $expected['alias'][] = ['value' => $v]; + } + + while ($cnt2-- > 0) { + $em->persist(new User\Email($user, $v = (string) ++$i)); + $expected['email'][] = ['value' => $v]; + } + + while ($cnt3-- > 0) { + $em->persist(new User\Phone($user, $v = (string) ++$i)); + $expected['phone'][] = ['value' => $v]; + } + $em->run(); + + $db = $this->orm->getSource(User::class)->getDatabase(); + self::assertSame( + $expected, + [ + 'alias' => $db->select('value')->from('user_alias')->fetchAll(), + 'email' => $db->select('value')->from('user_email')->fetchAll(), + 'phone' => $db->select('value')->from('user_phone')->fetchAll(), + ] + ); + } + + public function dataMatrix(): iterable + { + yield [2, 2, 2]; + yield [3, 3, 1]; + yield [1, 7, 4]; + } + + private function makeTables(): void + { + // Make tables + $this->makeTable('user', [ + 'id' => 'primary', // autoincrement + 'username' => 'string', + 'age' => 'int', + ]); + + $this->makeTable('user_alias', [ + 'id' => 'primary', + 'value' => 'string', + 'user_id' => 'int', + ]); + $this->makeFK('user_alias', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + + $this->makeTable('user_email', [ + 'id' => 'primary', + 'value' => 'string', + 'user_id' => 'int', + ]); + $this->makeFK('user_email', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + + $this->makeTable('user_phone', [ + 'id' => 'primary', + 'value' => 'string', + 'user_id' => 'int', + ]); + $this->makeFK('user_phone', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + } + + private function fillData(): void + { + $this->getDatabase()->table('user')->delete(); + $this->getDatabase()->table('user_alias')->delete(); + $this->getDatabase()->table('user_email')->delete(); + $this->getDatabase()->table('user_phone')->delete(); + + $this->getDatabase() + ->table('user') + ->insertOne([ + 'username' => 'nobody', + 'age' => 0, + ]); + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php new file mode 100644 index 00000000..bd7f9ef5 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php @@ -0,0 +1,19 @@ +username = $username; + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php new file mode 100644 index 00000000..8c40aea3 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php @@ -0,0 +1,22 @@ +user = $user; + $this->value = $value; + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php new file mode 100644 index 00000000..1840392d --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php @@ -0,0 +1,22 @@ +user = $user; + $this->value = $value; + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php new file mode 100644 index 00000000..b0b87993 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php @@ -0,0 +1,22 @@ +user = $user; + $this->value = $value; + } +} diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php new file mode 100644 index 00000000..20fac7a6 --- /dev/null +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php @@ -0,0 +1,178 @@ + [ + Schema::ENTITY => User::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + 'username' => 'username', + 'age' => 'age', + ], + Schema::RELATIONS => [ + 'aliases' => [ + Relation::TYPE => Relation::HAS_MANY, + Relation::TARGET => 'alias', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => ['id'], + Relation::OUTER_KEY => ['user_id'], + ], + ], + 'emails' => [ + Relation::TYPE => Relation::HAS_MANY, + Relation::TARGET => 'email', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => 'id', + Relation::OUTER_KEY => ['user_id'], + ], + ], + 'phones' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'phone', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => 'id', + Relation::OUTER_KEY => ['user_id'], + ], + ], + ], + Schema::TYPECAST => [ + 'id' => 'int', + 'age' => 'int', + ], + Schema::SCHEMA => [], + ], + + 'alias' => [ + Schema::ENTITY => User\Alias::class, + Schema::SOURCE => Source::class, + Schema::DATABASE => 'default', + Schema::MAPPER => Mapper::class, + Schema::TABLE => 'user_alias', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + 'value' => 'value', + 'user_id' => 'user_id', + ], + Schema::RELATIONS => [ + 'user' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'user', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => 'user_id', + Relation::OUTER_KEY => ['id'], + ], + ], + ], + Schema::TYPECAST => [ + 'id' => 'int', + 'value' => 'string', + 'user_id' => 'int', + ], + Schema::SCHEMA => [], + ], + + 'email' => [ + Schema::ENTITY => User\Email::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::REPOSITORY => Repository::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user_email', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + 'value' => 'value', + 'user_id' => 'user_id', + ], + Schema::RELATIONS => [ + 'user' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'user', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => 'user_id', + Relation::OUTER_KEY => ['id'], + ], + ], + ], + Schema::SCOPE => null, + Schema::TYPECAST => [ + 'id' => 'int', + 'value' => 'string', + 'user_id' => 'int', + ], + Schema::SCHEMA => [], + ], + + 'phone' => [ + Schema::ENTITY => User\Phone::class, + Schema::MAPPER => Mapper::class, + Schema::SOURCE => Source::class, + Schema::REPOSITORY => Repository::class, + Schema::DATABASE => 'default', + Schema::TABLE => 'user_phone', + Schema::PRIMARY_KEY => ['id'], + Schema::FIND_BY_KEYS => ['id'], + Schema::COLUMNS => [ + 'id' => 'id', + 'value' => 'value', + 'user_id' => 'user_id', + ], + Schema::RELATIONS => [ + 'user' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'user', + Relation::LOAD => Relation::LOAD_PROMISE, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::NULLABLE => false, + Relation::INNER_KEY => 'user_id', + Relation::OUTER_KEY => ['id'], + ], + ], + ], + Schema::SCOPE => null, + Schema::TYPECAST => [ + 'id' => 'int', + 'value' => 'string', + 'user_id' => 'int', + ], + Schema::SCHEMA => [], + ], + +]; diff --git a/tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php new file mode 100644 index 00000000..fb205973 --- /dev/null +++ b/tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php @@ -0,0 +1,17 @@ + Date: Fri, 25 Nov 2022 18:30:54 +0300 Subject: [PATCH 02/10] [tests] broken insert order --- .../Common/Integration/Case4/CaseTest.php | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php index 65a56218..2184e973 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php @@ -50,22 +50,22 @@ public function testOnce(): void self::assertSame( [ 'alias' => [ - ['value' => '1'], - ['value' => '2'], + ['id' => 1, 'value' => '1'], + ['id' => 2, 'value' => '2'], ], 'email' => [ - ['value' => '3'], - ['value' => '4'], + ['id' => 1, 'value' => '3'], + ['id' => 2, 'value' => '4'], ], 'phone' => [ - ['value' => '5'], - ['value' => '6'], + ['id' => 1, 'value' => '5'], + ['id' => 2, 'value' => '6'], ], ], [ - 'alias' => $db->select('value')->from('user_alias')->fetchAll(), - 'email' => $db->select('value')->from('user_email')->fetchAll(), - 'phone' => $db->select('value')->from('user_phone')->fetchAll(), + 'alias' => $db->select('id', 'value')->from('user_alias')->fetchAll(), + 'email' => $db->select('id', 'value')->from('user_email')->fetchAll(), + 'phone' => $db->select('id', 'value')->from('user_phone')->fetchAll(), ] ); } @@ -84,19 +84,19 @@ public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void $i = 0; $expected = []; - while ($cnt1-- > 0) { + for ($id = 1; $id <= $cnt1; $id++) { $em->persist(new User\Alias($user, $v = (string) ++$i)); - $expected['alias'][] = ['value' => $v]; + $expected['alias'][] = ['id' => $id, 'value' => $v]; } - while ($cnt2-- > 0) { + for ($id = 1; $id <= $cnt2; $id++) { $em->persist(new User\Email($user, $v = (string) ++$i)); - $expected['email'][] = ['value' => $v]; + $expected['email'][] = ['id' => $id, 'value' => $v]; } - while ($cnt3-- > 0) { + for ($id = 1; $id <= $cnt3; $id++) { $em->persist(new User\Phone($user, $v = (string) ++$i)); - $expected['phone'][] = ['value' => $v]; + $expected['phone'][] = ['id' => $id, 'value' => $v]; } $em->run(); @@ -104,9 +104,9 @@ public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void self::assertSame( $expected, [ - 'alias' => $db->select('value')->from('user_alias')->fetchAll(), - 'email' => $db->select('value')->from('user_email')->fetchAll(), - 'phone' => $db->select('value')->from('user_phone')->fetchAll(), + 'alias' => $db->select('id', 'value')->from('user_alias')->fetchAll(), + 'email' => $db->select('id', 'value')->from('user_email')->fetchAll(), + 'phone' => $db->select('id', 'value')->from('user_phone')->fetchAll(), ] ); } From ffc50050f1466867128d9667ef911232d4c35656 Mon Sep 17 00:00:00 2001 From: gam6itko Date: Fri, 25 Nov 2022 19:15:58 +0300 Subject: [PATCH 03/10] [tests] broken insert order --- .../Driver/Common/Integration/Case4/CaseTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php index 2184e973..b02f1d81 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php @@ -63,9 +63,9 @@ public function testOnce(): void ], ], [ - 'alias' => $db->select('id', 'value')->from('user_alias')->fetchAll(), - 'email' => $db->select('id', 'value')->from('user_email')->fetchAll(), - 'phone' => $db->select('id', 'value')->from('user_phone')->fetchAll(), + 'alias' => $db->select('id', 'value')->from('user_alias')->orderBy('id')->fetchAll(), + 'email' => $db->select('id', 'value')->from('user_email')->orderBy('id')->fetchAll(), + 'phone' => $db->select('id', 'value')->from('user_phone')->orderBy('id')->fetchAll(), ] ); } @@ -104,9 +104,9 @@ public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void self::assertSame( $expected, [ - 'alias' => $db->select('id', 'value')->from('user_alias')->fetchAll(), - 'email' => $db->select('id', 'value')->from('user_email')->fetchAll(), - 'phone' => $db->select('id', 'value')->from('user_phone')->fetchAll(), + 'alias' => $db->select('id', 'value')->from('user_alias')->orderBy('id')->fetchAll(), + 'email' => $db->select('id', 'value')->from('user_email')->orderBy('id')->fetchAll(), + 'phone' => $db->select('id', 'value')->from('user_phone')->orderBy('id')->fetchAll(), ] ); } From e8bf043ac597484068721c88630b156a5dc530ba Mon Sep 17 00:00:00 2001 From: gam6itko Date: Sat, 26 Nov 2022 10:16:41 +0300 Subject: [PATCH 04/10] [tests] broken insert order --- .../Common/Integration/Case4/CaseTest.php | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php index b02f1d81..5947605a 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php @@ -46,7 +46,6 @@ public function testOnce(): void $em->run(); - $db = $this->orm->getSource(User::class)->getDatabase(); self::assertSame( [ 'alias' => [ @@ -63,9 +62,9 @@ public function testOnce(): void ], ], [ - 'alias' => $db->select('id', 'value')->from('user_alias')->orderBy('id')->fetchAll(), - 'email' => $db->select('id', 'value')->from('user_email')->orderBy('id')->fetchAll(), - 'phone' => $db->select('id', 'value')->from('user_phone')->orderBy('id')->fetchAll(), + 'alias' => $this->fetchFromTable('user_alias'), + 'email' => $this->fetchFromTable('user_email'), + 'phone' => $this->fetchFromTable('user_phone'), ] ); } @@ -100,13 +99,12 @@ public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void } $em->run(); - $db = $this->orm->getSource(User::class)->getDatabase(); self::assertSame( $expected, [ - 'alias' => $db->select('id', 'value')->from('user_alias')->orderBy('id')->fetchAll(), - 'email' => $db->select('id', 'value')->from('user_email')->orderBy('id')->fetchAll(), - 'phone' => $db->select('id', 'value')->from('user_phone')->orderBy('id')->fetchAll(), + 'alias' => $this->fetchFromTable('user_alias'), + 'email' => $this->fetchFromTable('user_email'), + 'phone' => $this->fetchFromTable('user_phone'), ] ); } @@ -118,6 +116,17 @@ public function dataMatrix(): iterable yield [1, 7, 4]; } + private function fetchFromTable(string $tableName): array + { + $db = $this->orm->getSource(User::class)->getDatabase(); + $rows = $db->select('id', 'value')->from($tableName)->orderBy('id')->fetchAll(); + // cast id to int specially for mssql + return array_map(function (array $row): array { + $row['id'] = (int) $row['id']; + return $row; + }, $rows); + } + private function makeTables(): void { // Make tables From 523c71ee9ea7a1d2f6131f029e445f45b9c99879 Mon Sep 17 00:00:00 2001 From: gam6itko Date: Tue, 27 Dec 2022 13:50:36 +0300 Subject: [PATCH 05/10] move tests to folder Issue380 --- .../Driver/Common/Integration/{Case4 => Issue380}/CaseTest.php | 0 .../Common/Integration/{Case4 => Issue380}/Entity/User.php | 0 .../Integration/{Case4 => Issue380}/Entity/User/Alias.php | 0 .../Integration/{Case4 => Issue380}/Entity/User/Email.php | 0 .../Integration/{Case4 => Issue380}/Entity/User/Phone.php | 0 .../Driver/Common/Integration/{Case4 => Issue380}/schema.php | 0 .../Driver/MySQL/Integration/{Case4 => Issue380}/CaseTest.php | 2 +- .../Postgres/Integration/{Case4 => Issue380}/CaseTest.php | 2 +- .../SQLServer/Integration/{Case4 => Issue380}/CaseTest.php | 2 +- .../Driver/SQLite/Integration/{Case4 => Issue380}/CaseTest.php | 2 +- 10 files changed, 4 insertions(+), 4 deletions(-) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/CaseTest.php (100%) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/Entity/User.php (100%) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/Entity/User/Alias.php (100%) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/Entity/User/Email.php (100%) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/Entity/User/Phone.php (100%) rename tests/ORM/Functional/Driver/Common/Integration/{Case4 => Issue380}/schema.php (100%) rename tests/ORM/Functional/Driver/MySQL/Integration/{Case4 => Issue380}/CaseTest.php (78%) rename tests/ORM/Functional/Driver/Postgres/Integration/{Case4 => Issue380}/CaseTest.php (96%) rename tests/ORM/Functional/Driver/SQLServer/Integration/{Case4 => Issue380}/CaseTest.php (96%) rename tests/ORM/Functional/Driver/SQLite/Integration/{Case4 => Issue380}/CaseTest.php (78%) diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/CaseTest.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Alias.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Email.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/Entity/User/Phone.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php similarity index 100% rename from tests/ORM/Functional/Driver/Common/Integration/Case4/schema.php rename to tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php diff --git a/tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php similarity index 78% rename from tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php rename to tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php index fb205973..fff4a866 100644 --- a/tests/ORM/Functional/Driver/MySQL/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\MySQL\Integration\Case4; +namespace Cycle\ORM\Tests\Functional\Driver\MySQL\Integration\Issue380; // phpcs:ignore use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; diff --git a/tests/ORM/Functional/Driver/Postgres/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php similarity index 96% rename from tests/ORM/Functional/Driver/Postgres/Integration/Case4/CaseTest.php rename to tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php index 7db2b2be..38d5ea62 100644 --- a/tests/ORM/Functional/Driver/Postgres/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Postgres\Integration\Case4; +namespace Cycle\ORM\Tests\Functional\Driver\Postgres\Integration\Issue380; // phpcs:ignore use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; diff --git a/tests/ORM/Functional/Driver/SQLServer/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php similarity index 96% rename from tests/ORM/Functional/Driver/SQLServer/Integration/Case4/CaseTest.php rename to tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php index 7acb2a12..aa0e4c0d 100644 --- a/tests/ORM/Functional/Driver/SQLServer/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\SQLServer\Integration\Case4; +namespace Cycle\ORM\Tests\Functional\Driver\SQLServer\Integration\Issue380; // phpcs:ignore use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; diff --git a/tests/ORM/Functional/Driver/SQLite/Integration/Case4/CaseTest.php b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php similarity index 78% rename from tests/ORM/Functional/Driver/SQLite/Integration/Case4/CaseTest.php rename to tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php index d99d1dec..d0eb647e 100644 --- a/tests/ORM/Functional/Driver/SQLite/Integration/Case4/CaseTest.php +++ b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\SQLite\Integration\Case4; +namespace Cycle\ORM\Tests\Functional\Driver\SQLite\Integration\Issue380; // phpcs:ignore use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; From 585bf62fc8158faf1c0602e7300a7ff8d29efa67 Mon Sep 17 00:00:00 2001 From: gam6itko Date: Mon, 29 Jan 2024 19:16:27 +0300 Subject: [PATCH 06/10] delete-insert with unique key --- CONTRIBUTING.md | 2 +- phpunit.xml | 4 +- .../Common/Integration/Issue380/CaseTest.php | 125 +++++++++++++++--- .../Integration/Issue380/Entity/User.php | 2 +- .../Issue380/Entity/User/Alias.php | 4 +- .../Issue380/Entity/User/Email.php | 4 +- .../Issue380/Entity/User/Phone.php | 4 +- .../Common/Integration/Issue380/schema.php | 6 +- .../MySQL/Integration/Issue380/CaseTest.php | 2 +- .../Integration/Issue380/CaseTest.php | 2 +- .../Integration/Issue380/CaseTest.php | 2 +- .../SQLite/Integration/Issue380/CaseTest.php | 2 +- 12 files changed, 123 insertions(+), 36 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e60f0b8e..4bb3037f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ $ ./vendor/bin/phpunit To run quick test suite: ```bash -$ ./vendor/bin/phpunit tests/ORM/Driver/SQLite +$ ./vendor/bin/phpunit tests/ORM/Functional/Driver/SQLite ``` ## Help Needed In diff --git a/phpunit.xml b/phpunit.xml index bbc40674..7ed98adb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,8 +11,8 @@ convertWarningsToExceptions="true" convertDeprecationsToExceptions="true" processIsolation="false" - stopOnFailure="true" - stopOnError="true" + stopOnFailure="false" + stopOnError="false" > diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php index 5947605a..88262639 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/CaseTest.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4; +namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380; +use Cycle\Database\Exception\StatementException\ConstrainException; use Cycle\ORM\EntityManager; use Cycle\ORM\Tests\Functional\Driver\Common\BaseTest; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; use Cycle\ORM\Tests\Functional\Driver\Common\Integration\IntegrationTestTrait; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; use Cycle\ORM\Tests\Traits\TableTrait; abstract class CaseTest extends BaseTest @@ -22,10 +23,10 @@ public function setUp(): void $this->makeTables(); $this->fillData(); - $this->loadSchema(__DIR__.'/schema.php'); + $this->loadSchema(__DIR__ . '/schema.php'); } - public function testOnce(): void + public function testInsertOnce(): void { /** @var User $user */ $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); @@ -35,14 +36,14 @@ public function testOnce(): void $i = 0; - $em->persist(new User\Alias($user, (string) ++$i)); - $em->persist(new User\Alias($user, (string) ++$i)); + $em->persist(new User\Alias($user, (string)++$i)); + $em->persist(new User\Alias($user, (string)++$i)); - $em->persist(new User\Email($user, (string) ++$i)); - $em->persist(new User\Email($user, (string) ++$i)); + $em->persist(new User\Email($user, (string)++$i)); + $em->persist(new User\Email($user, (string)++$i)); - $em->persist(new User\Phone($user, (string) ++$i)); - $em->persist(new User\Phone($user, (string) ++$i)); + $em->persist(new User\Phone($user, (string)++$i)); + $em->persist(new User\Phone($user, (string)++$i)); $em->run(); @@ -72,7 +73,7 @@ public function testOnce(): void /** * @dataProvider dataMatrix */ - public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void + public function testInsertMatrix(int $cnt1, int $cnt2, int $cnt3): void { /** @var User $user */ $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); @@ -84,17 +85,17 @@ public function testMatrix(int $cnt1, int $cnt2, int $cnt3): void $expected = []; for ($id = 1; $id <= $cnt1; $id++) { - $em->persist(new User\Alias($user, $v = (string) ++$i)); + $em->persist(new User\Alias($user, $v = (string)++$i)); $expected['alias'][] = ['id' => $id, 'value' => $v]; } for ($id = 1; $id <= $cnt2; $id++) { - $em->persist(new User\Email($user, $v = (string) ++$i)); + $em->persist(new User\Email($user, $v = (string)++$i)); $expected['email'][] = ['id' => $id, 'value' => $v]; } for ($id = 1; $id <= $cnt3; $id++) { - $em->persist(new User\Phone($user, $v = (string) ++$i)); + $em->persist(new User\Phone($user, $v = (string)++$i)); $expected['phone'][] = ['id' => $id, 'value' => $v]; } $em->run(); @@ -116,13 +117,100 @@ public function dataMatrix(): iterable yield [1, 7, 4]; } + public function testFailOnInsertUniqueDuplicate(): void + { + self::expectException(ConstrainException::class); + + /** @var User $user */ + $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); + + // insert unique + $em = (new EntityManager($this->orm)); + $em->persist($user); + $em + ->persist(new User\Alias($user, '1')) + ->persist(new User\Alias($user, '1')) + ->run(); + } + + public function testDeleteAndInsertFainOnDuplicateUniqueKey(): void + { + /** @var User $user */ + $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); + + // insert unique + $em = (new EntityManager($this->orm)); + $em->persist($user); + $em + ->persist($a0 = new User\Alias($user, '1')) + ->persist($a1 = new User\Alias($user, '2')); + $em + ->persist($e0 = new User\Email($user, '1')) + ->persist($e1 = new User\Email($user, '2')); + $em + ->persist($p0 = new User\Phone($user, '1')) + ->persist($p1 = new User\Phone($user, '2')); + $em->run(); + + self::assertCount(2, \array_intersect([1, 2], [$a0->id, $a1->id])); + self::assertCount(2, \array_intersect([1, 2], [$e0->id, $e1->id])); + self::assertCount(2, \array_intersect([1, 2], [$p0->id, $p1->id])); + unset($a0, $a1, $e0, $e1, $p0, $p1); + + $this->orm->getHeap()->clean(); + + // delete old and persist new with same unique value + $em = (new EntityManager($this->orm)); + + /** @var User $user */ + $user = $this->orm->getRepository(User::class)->findOne(['id' => 1]); + $user->username = 'up-username'; + $em->persist($user); + + $a0 = $this->orm->get(User\Alias::class, ['id' => 1]); + $a1 = $this->orm->get(User\Alias::class, ['id' => 2]); + + $e0 = $this->orm->get(User\Email::class, ['id' => 1]); + $e1 = $this->orm->get(User\Email::class, ['id' => 2]); + + $p0 = $this->orm->get(User\Phone::class, ['id' => 1]); + $p1 = $this->orm->get(User\Phone::class, ['id' => 2]); + + $em + ->delete($a0) + ->delete($a1); + $em + ->persist(new User\Alias($user, '1')) + ->persist(new User\Alias($user, '2')); + $em + ->delete($e0) + ->delete($e1); + $em + ->persist(new User\Email($user, '1')) + ->persist(new User\Email($user, '2')); + $em + ->delete($p0) + ->delete($p1); + $em + ->persist(new User\Phone($user, '1')) + ->persist(new User\Phone($user, '2')); + + $em->run(); + + self::assertTrue(true); + } + private function fetchFromTable(string $tableName): array { $db = $this->orm->getSource(User::class)->getDatabase(); - $rows = $db->select('id', 'value')->from($tableName)->orderBy('id')->fetchAll(); + $rows = $db + ->select('id', 'value') + ->from($tableName) + ->orderBy('id') + ->fetchAll(); // cast id to int specially for mssql - return array_map(function (array $row): array { - $row['id'] = (int) $row['id']; + return \array_map(function (array $row): array { + $row['id'] = (int)$row['id']; return $row; }, $rows); } @@ -142,6 +230,7 @@ private function makeTables(): void 'user_id' => 'int', ]); $this->makeFK('user_alias', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + $this->makeIndex('user_alias', ['value'], true); $this->makeTable('user_email', [ 'id' => 'primary', @@ -149,6 +238,7 @@ private function makeTables(): void 'user_id' => 'int', ]); $this->makeFK('user_email', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + $this->makeIndex('user_email', ['value'], true); $this->makeTable('user_phone', [ 'id' => 'primary', @@ -156,6 +246,7 @@ private function makeTables(): void 'user_id' => 'int', ]); $this->makeFK('user_phone', 'user_id', 'user', 'id', 'NO ACTION', 'NO ACTION'); + $this->makeIndex('user_phone', ['value'], true); } private function fillData(): void diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php index bd7f9ef5..89750e5c 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity; +namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity; class User { diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php index 8c40aea3..f99924a6 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Alias.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; class Alias { diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php index 1840392d..ae020fc0 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Email.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; class Email { diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php index b0b87993..afa73a30 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/Entity/User/Phone.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +namespace Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; class Phone { diff --git a/tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php b/tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php index 20fac7a6..3762a923 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Issue380/schema.php @@ -7,11 +7,7 @@ use Cycle\ORM\SchemaInterface as Schema; use Cycle\ORM\Select\Repository; use Cycle\ORM\Select\Source; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\Comment; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\Post; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\PostTag; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\Tag; -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Entity\User; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\Entity\User; return [ 'user' => [ diff --git a/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php index fff4a866..3e092646 100644 --- a/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/MySQL/Integration/Issue380/CaseTest.php @@ -5,7 +5,7 @@ namespace Cycle\ORM\Tests\Functional\Driver\MySQL\Integration\Issue380; // phpcs:ignore -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\CaseTest as CommonClass; /** * @group driver diff --git a/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php index 38d5ea62..9256dc9c 100644 --- a/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/Postgres/Integration/Issue380/CaseTest.php @@ -5,7 +5,7 @@ namespace Cycle\ORM\Tests\Functional\Driver\Postgres\Integration\Issue380; // phpcs:ignore -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\CaseTest as CommonClass; /** * @group driver diff --git a/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php index aa0e4c0d..8f9066ae 100644 --- a/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/SQLServer/Integration/Issue380/CaseTest.php @@ -5,7 +5,7 @@ namespace Cycle\ORM\Tests\Functional\Driver\SQLServer\Integration\Issue380; // phpcs:ignore -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\CaseTest as CommonClass; /** * @group driver diff --git a/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php index d0eb647e..39a558f2 100644 --- a/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php @@ -5,7 +5,7 @@ namespace Cycle\ORM\Tests\Functional\Driver\SQLite\Integration\Issue380; // phpcs:ignore -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\CaseTest as CommonClass; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Issue380 as CommonClass; /** * @group driver From 60bf1c25d29bbb22fe0a0d7b1e5e615e528e8c6f Mon Sep 17 00:00:00 2001 From: gam6itko Date: Mon, 29 Jan 2024 19:32:34 +0300 Subject: [PATCH 07/10] namespace fix --- .../Functional/Driver/SQLite/Integration/Issue380/CaseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php index 39a558f2..08a7a65c 100644 --- a/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php +++ b/tests/ORM/Functional/Driver/SQLite/Integration/Issue380/CaseTest.php @@ -5,7 +5,7 @@ namespace Cycle\ORM\Tests\Functional\Driver\SQLite\Integration\Issue380; // phpcs:ignore -use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Case4\Issue380 as CommonClass; +use Cycle\ORM\Tests\Functional\Driver\Common\Integration\Issue380\CaseTest as CommonClass; /** * @group driver From a6662afd4a9b14c4c5acd6e156b955af7d0bad0d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 13 Feb 2024 01:43:56 +0400 Subject: [PATCH 08/10] Add TupleStorage --- src/Transaction/Pool.php | 88 +++++------- src/Transaction/TupleStorage.php | 69 ++++++++++ .../ORM/Unit/Transaction/TupleStorageTest.php | 127 ++++++++++++++++++ 3 files changed, 228 insertions(+), 56 deletions(-) create mode 100644 src/Transaction/TupleStorage.php create mode 100644 tests/ORM/Unit/Transaction/TupleStorageTest.php diff --git a/src/Transaction/Pool.php b/src/Transaction/Pool.php index 2f62d539..8100d603 100644 --- a/src/Transaction/Pool.php +++ b/src/Transaction/Pool.php @@ -10,7 +10,6 @@ use Cycle\ORM\ORMInterface; use Cycle\ORM\Reference\ReferenceInterface; use JetBrains\PhpStorm\ExpectedValues; -use SplObjectStorage; use Traversable; /** @@ -21,14 +20,9 @@ */ final class Pool implements \Countable { - /** @var SplObjectStorage */ - private SplObjectStorage $storage; - - /** @var SplObjectStorage */ - private SplObjectStorage $all; - - /** @var SplObjectStorage */ - private SplObjectStorage $priorityStorage; + private TupleStorage $storage; + private TupleStorage $all; + private TupleStorage $priorityStorage; /** * @var Tuple[] @@ -49,8 +43,8 @@ final class Pool implements \Countable public function __construct( private ORMInterface $orm ) { - $this->storage = new SplObjectStorage(); - $this->all = new SplObjectStorage(); + $this->storage = new TupleStorage(); + $this->all = new TupleStorage(); } public function someHappens(): void @@ -93,13 +87,13 @@ public function attach( private function smartAttachTuple(Tuple $tuple, bool $highPriority = false, bool $snap = false): Tuple { if ($tuple->status === Tuple::STATUS_PROCESSED) { - $this->all->attach($tuple->entity, $tuple); + $this->all->attach($tuple); return $tuple; } if ($tuple->status === Tuple::STATUS_PREPARING && $this->all->contains($tuple->entity)) { - return $this->all->offsetGet($tuple->entity); + return $this->all->getTuple($tuple->entity); } - $this->all->attach($tuple->entity, $tuple); + $this->all->attach($tuple); if ($this->iterating || $snap) { $this->snap($tuple); @@ -109,9 +103,9 @@ private function smartAttachTuple(Tuple $tuple, bool $highPriority = false, bool $tuple->state->setStatus(Node::SCHEDULED_DELETE); } if (($this->priorityAutoAttach || $highPriority) && $tuple->status === Tuple::STATUS_PREPARING) { - $this->priorityStorage->attach($tuple->entity, $tuple); + $this->priorityStorage->attach($tuple); } else { - $this->storage->attach($tuple->entity, $tuple); + $this->storage->attach($tuple); } return $tuple; } @@ -138,7 +132,7 @@ public function attachDelete( public function offsetGet(object $entity): ?Tuple { - return $this->all->contains($entity) ? $this->all->offsetGet($entity) : null; + return $this->all->contains($entity) ? $this->all->getTuple($entity) : null; } /** @@ -156,14 +150,11 @@ public function openIterator(): Traversable $this->unprocessed = []; // Snap all entities before store - while ($this->storage->valid()) { - /** @var Tuple $tuple */ - $tuple = $this->storage->getInfo(); + /** @var object $entity */ + foreach ($this->storage as $entity => $tuple) { $this->snap($tuple); if (!isset($tuple->node)) { - $this->storage->detach($this->storage->current()); - } else { - $this->storage->next(); + $this->storage->detach($entity); } } @@ -172,8 +163,7 @@ public function openIterator(): Traversable // High priority first if ($this->priorityStorage->count() > 0) { $priorityStorage = $this->priorityStorage; - foreach ($priorityStorage as $entity) { - $tuple = $priorityStorage->offsetGet($entity); + foreach ($priorityStorage as $entity => $tuple) { yield $entity => $tuple; $this->trashIt($entity, $tuple, $priorityStorage); } @@ -184,21 +174,13 @@ public function openIterator(): Traversable break; } $pool = $this->storage; - if (!$pool->valid() && $pool->count() > 0) { - $pool->rewind(); - } if ($stage === 0) { - // foreach ($pool as $entity) { - while ($pool->valid()) { - /** @var Tuple $tuple */ - $entity = $pool->current(); - $tuple = $pool->getInfo(); - $pool->next(); + foreach ($this->storage as $entity => $tuple) { if ($tuple->status !== Tuple::STATUS_PREPARING) { continue; } yield $entity => $tuple; - $this->trashIt($entity, $tuple, $this->storage); + $this->trashIt($entity, $tuple, $pool); // Check priority if ($this->priorityStorage->count() > 0) { continue 2; @@ -206,14 +188,10 @@ public function openIterator(): Traversable } $this->priorityAutoAttach = true; $stage = 1; - $this->storage->rewind(); } if ($stage === 1) { - while ($pool->valid()) { - /** @var Tuple $tuple */ - $entity = $pool->current(); - $tuple = $pool->getInfo(); - $pool->next(); + /** @var object $entity */ + foreach ($this->storage as $entity => $tuple) { if ($tuple->status !== Tuple::STATUS_WAITING || $tuple->task === Tuple::TASK_DELETE) { continue; } @@ -226,14 +204,11 @@ public function openIterator(): Traversable } } $stage = 2; - $this->storage->rewind(); } if ($stage === 2) { $this->happens = 0; - while ($pool->valid()) { - /** @var Tuple $tuple */ - $entity = $pool->current(); - $tuple = $pool->getInfo(); + /** @var object $entity */ + foreach ($this->storage as $entity => $tuple) { if ($tuple->task === Tuple::TASK_DELETE) { $tuple->task = Tuple::TASK_FORCE_DELETE; } @@ -242,7 +217,6 @@ public function openIterator(): Traversable } elseif ($tuple->status === Tuple::STATUS_DEFERRED) { $tuple->status = Tuple::STATUS_PROPOSED; } - $pool->next(); yield $entity => $tuple; $this->trashIt($entity, $tuple, $this->storage); // Check priority @@ -254,7 +228,7 @@ public function openIterator(): Traversable if ($this->happens !== 0 && $hasUnresolved) { /** @psalm-suppress InvalidIterator */ foreach ($this->unprocessed as $item) { - $this->storage->attach($item->entity, $item); + $this->storage->attach($item); } $this->unprocessed = []; continue; @@ -290,8 +264,7 @@ public function getUnresolved(): iterable */ public function getAllTuples(): iterable { - foreach ($this->all as $entity) { - $tuple = $this->all->offsetGet($entity); + foreach ($this->all as $entity => $tuple) { if (isset($tuple->node)) { yield $entity => $tuple; } @@ -307,7 +280,6 @@ public function closeIterator(): void $this->priorityEnabled = false; $this->priorityAutoAttach = false; unset($this->priorityStorage, $this->unprocessed); - // $this->all = new SplObjectStorage(); } /** @@ -346,17 +318,17 @@ private function snap(Tuple $tuple, bool $forceUpdateState = false): void } } - private function trashIt(object $entity, Tuple $tuple, SplObjectStorage $storage): void + private function trashIt(object $entity, Tuple $tuple, TupleStorage $storage): void { - $storage->detach($entity); - if ($tuple->status === Tuple::STATUS_UNPROCESSED) { + $storage->detach($entity); $tuple->status = Tuple::STATUS_PREPROCESSED; $this->unprocessed[] = $tuple; return; } if ($tuple->status >= Tuple::STATUS_PREPROCESSED) { + $storage->detach($entity); $tuple->status = Tuple::STATUS_PROCESSED; ++$this->happens; return; @@ -366,7 +338,11 @@ private function trashIt(object $entity, Tuple $tuple, SplObjectStorage $storage ++$tuple->status; ++$this->happens; } - $this->storage->attach($tuple->entity, $tuple); + + if ($storage !== $this->storage) { + $storage->detach($entity); + $this->storage->attach($tuple); + } } private function activatePriorityStorage(): void @@ -375,7 +351,7 @@ private function activatePriorityStorage(): void return; } $this->priorityEnabled = true; - $this->priorityStorage = new SplObjectStorage(); + $this->priorityStorage = new TupleStorage(); } private function updateTuple(Tuple $tuple, int $task, ?int $status, bool $cascade, ?Node $node, ?State $state): void diff --git a/src/Transaction/TupleStorage.php b/src/Transaction/TupleStorage.php new file mode 100644 index 00000000..675ff039 --- /dev/null +++ b/src/Transaction/TupleStorage.php @@ -0,0 +1,69 @@ + + */ +final class TupleStorage implements IteratorAggregate, Countable +{ + /** @var SplObjectStorage */ + private SplObjectStorage $storage; + + public function __construct() + { + $this->storage = new SplObjectStorage(); + } + + public function getIterator(): \Traversable + { + $this->storage->rewind(); + + while ($this->storage->valid()) { + $entity = $this->storage->current(); + $tuple = $this->storage->getInfo(); + $this->storage->next(); + yield $entity => $tuple; + } + } + + /** + * Returns {@see Tuple} if exists, throws an exception otherwise. + * + * @throws \Throwable if the entity is not found in the storage + */ + public function getTuple(object $entity): Tuple + { + return $this->storage->offsetGet($entity); + } + + public function attach(Tuple $tuple): void + { + $this->storage->attach($tuple->entity, $tuple); + } + + public function contains(object $entity): bool + { + return $this->storage->contains($entity); + } + + public function detach(object $entity): void + { + $this->storage->offsetUnset($entity); + } + + /** + * @return int<0, max> + */ + public function count(): int + { + return $this->storage->count(); + } +} diff --git a/tests/ORM/Unit/Transaction/TupleStorageTest.php b/tests/ORM/Unit/Transaction/TupleStorageTest.php new file mode 100644 index 00000000..3686a36f --- /dev/null +++ b/tests/ORM/Unit/Transaction/TupleStorageTest.php @@ -0,0 +1,127 @@ +assertCount(0, $storage); + $this->assertFalse($storage->contains($entity)); + $this->expectException(\Throwable::class); + $storage->getTuple(new \stdClass()); + } + + public function testAttachedEntity(): void + { + $storage = new TupleStorage(); + $tuple = $this->createTuple($entity = new \stdClass()); + + $storage->attach($tuple); + + self::assertCount(1, $storage); + self::assertTrue($storage->contains($entity)); + self::assertSame($tuple, $storage->getTuple($entity)); + + $storage->detach($entity); + + self::assertCount(0, $storage); + self::assertFalse($storage->contains($entity)); + $this->expectException(\Throwable::class); + $storage->getTuple($entity); + } + + public function testSequence(): void + { + $storage = new TupleStorage(); + $tuples = []; + for ($i = 0; $i < 100; $i++) { + $tuples[] = $this->createTuple(new \stdClass()); + } + // Randomize the order + \shuffle($tuples); + + // Store all + foreach ($tuples as $tuple) { + $storage->attach($tuple); + } + // and detach each second + $toRestore = []; + foreach ($tuples as $k => $tuple) { + if ($k % 2 === 0) { + $storage->detach($tuple->entity); + unset($tuples[$k]); + } + } + // and return each fourth + foreach ($toRestore as $k => $tuple) { + if ($k % 2 === 0) { + $storage->attach($tuple); + $tuples[] = $tuple; + } + } + + self::assertCount(\count($tuples), $storage); + foreach ($tuples as $tuple) { + self::assertTrue($storage->contains($tuple->entity)); + self::assertSame($tuple, $storage->getTuple($tuple->entity)); + } + + $collection = []; + foreach ($storage as $entity => $tuple) { + self::assertSame($entity, $tuple->entity); + $collection[] = $tuple; + } + + self::assertSame(\array_values($tuples), $collection); + } + + public function testAddItemsWhenIterating(): void + { + $storage = new TupleStorage(); + $tuples = []; + for ($i = 0; $i < 10; $i++) { + $tuples[] = $this->createTuple(new \stdClass()); + } + // Randomize the order + \shuffle($tuples); + + // Store all + foreach ($tuples as $tuple) { + $storage->attach($tuple); + } + + $collection = []; + $i = 0; + foreach ($storage as $entity => $tuple) { + $collection[] = $tuple; + self::assertTrue($storage->contains($tuple->entity)); + self::assertSame($tuple, $storage->getTuple($tuple->entity)); + + // Add a new item after each 10th iteration + if (++$i % 10 === 0) { + $newTuple = $this->createTuple(new \stdClass()); + $storage->attach($newTuple); + $tuples[] = $newTuple; + } + } + + self::assertCount(\count($tuples), $storage); + self::assertCount(\count($tuples), $collection); + self::assertSame(\array_values($tuples), $collection); + } + + private function createTuple(object $entity): Tuple + { + return new Tuple(Tuple::TASK_STORE, $entity, true, Tuple::STATUS_PREPARING); + } +} From 814e84e90768075e89b230c245875679fe20e315 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 13 Feb 2024 13:59:40 +0400 Subject: [PATCH 09/10] Add TupleStorage --- CHANGELOG.md | 4 + src/Transaction/Pool.php | 2 + src/Transaction/TupleStorage.php | 56 ++++++--- .../Common/Integration/Case321/CaseTest.php | 1 - .../ORM/Unit/Transaction/TupleStorageTest.php | 109 +++++++++++++++--- 5 files changed, 136 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef221ed..caca5e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +v2.7.1 (13.02.2024) +-------------------- +- Fix inserting order in regular cases by @roxblnfk and @gam6itko (#381) + v2.7.0 (08.02.2024) -------------------- - Add Generated Fields option into ORM Schema by @roxblnfk (#462) diff --git a/src/Transaction/Pool.php b/src/Transaction/Pool.php index 8100d603..2d04dbe2 100644 --- a/src/Transaction/Pool.php +++ b/src/Transaction/Pool.php @@ -179,6 +179,7 @@ public function openIterator(): Traversable if ($tuple->status !== Tuple::STATUS_PREPARING) { continue; } + yield $entity => $tuple; $this->trashIt($entity, $tuple, $pool); // Check priority @@ -195,6 +196,7 @@ public function openIterator(): Traversable if ($tuple->status !== Tuple::STATUS_WAITING || $tuple->task === Tuple::TASK_DELETE) { continue; } + $tuple->status = Tuple::STATUS_WAITED; yield $entity => $tuple; $this->trashIt($entity, $tuple, $this->storage); diff --git a/src/Transaction/TupleStorage.php b/src/Transaction/TupleStorage.php index 675ff039..a51cea7e 100644 --- a/src/Transaction/TupleStorage.php +++ b/src/Transaction/TupleStorage.php @@ -6,7 +6,6 @@ use Countable; use IteratorAggregate; -use SplObjectStorage; /** * @internal @@ -14,23 +13,33 @@ */ final class TupleStorage implements IteratorAggregate, Countable { - /** @var SplObjectStorage */ - private SplObjectStorage $storage; + /** @var array */ + private array $storage = []; - public function __construct() - { - $this->storage = new SplObjectStorage(); - } + private array $iterators = []; + /** + * @return \Traversable + */ public function getIterator(): \Traversable { - $this->storage->rewind(); + $iterator = $this->storage; + // When the generator is destroyed, the reference to the iterator is removed from the collection. + $cleaner = new class () { + public array $iterators; + public function __destruct() + { + unset($this->iterators[\spl_object_id($this)]); + } + }; + /** @psalm-suppress UnsupportedPropertyReferenceUsage */ + $cleaner->iterators = &$this->iterators; + $this->iterators[\spl_object_id($cleaner)] = &$iterator; - while ($this->storage->valid()) { - $entity = $this->storage->current(); - $tuple = $this->storage->getInfo(); - $this->storage->next(); - yield $entity => $tuple; + while (\count($iterator) > 0) { + $tuple = \current($iterator); + unset($iterator[\key($iterator)]); + yield $tuple->entity => $tuple; } } @@ -41,22 +50,33 @@ public function getIterator(): \Traversable */ public function getTuple(object $entity): Tuple { - return $this->storage->offsetGet($entity); + return $this->storage[\spl_object_id($entity)] ?? throw new \RuntimeException('Tuple not found'); } public function attach(Tuple $tuple): void { - $this->storage->attach($tuple->entity, $tuple); + if ($this->contains($tuple->entity)) { + return; + } + + $this->storage[\spl_object_id($tuple->entity)] = $tuple; + foreach ($this->iterators as &$collection) { + $collection[\spl_object_id($tuple->entity)] = $tuple; + } } public function contains(object $entity): bool { - return $this->storage->contains($entity); + return \array_key_exists(\spl_object_id($entity), $this->storage); } public function detach(object $entity): void { - $this->storage->offsetUnset($entity); + $id = \spl_object_id($entity); + unset($this->storage[$id]); + foreach ($this->iterators as &$collection) { + unset($collection[$id]); + } } /** @@ -64,6 +84,6 @@ public function detach(object $entity): void */ public function count(): int { - return $this->storage->count(); + return \count($this->storage); } } diff --git a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php index 9f72f672..00c4c3f3 100644 --- a/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php +++ b/tests/ORM/Functional/Driver/Common/Integration/Case321/CaseTest.php @@ -84,7 +84,6 @@ private function makeTables(): void 'id' => 'primary', // autoincrement ]); - $this->logger->display(); $this->makeTable('user4', [ 'id' => 'primary', 'counter' => 'int', diff --git a/tests/ORM/Unit/Transaction/TupleStorageTest.php b/tests/ORM/Unit/Transaction/TupleStorageTest.php index 3686a36f..9954d624 100644 --- a/tests/ORM/Unit/Transaction/TupleStorageTest.php +++ b/tests/ORM/Unit/Transaction/TupleStorageTest.php @@ -88,36 +88,111 @@ public function testSequence(): void public function testAddItemsWhenIterating(): void { $storage = new TupleStorage(); - $tuples = []; for ($i = 0; $i < 10; $i++) { - $tuples[] = $this->createTuple(new \stdClass()); + $storage->attach($this->createTuple(new \stdClass())); } - // Randomize the order - \shuffle($tuples); - // Store all - foreach ($tuples as $tuple) { - $storage->attach($tuple); + /** @see TupleStorage::$iterators */ + self::assertCount(0, (fn(): array => $this->iterators)->call($storage)); + + $iterator = $storage->getIterator(); + // Start generator + foreach ($iterator as $item) { + break; + } + /** @see TupleStorage::$iterators */ + self::assertCount(1, (fn(): array => $this->iterators)->call($storage)); + + // Cleanup on iterator destruction + unset($iterator); + /** @see TupleStorage::$iterators */ + self::assertCount(0, (fn(): array => $this->iterators)->call($storage)); + + // Cleanup on end of iteration + $iterator = $storage->getIterator(); + // Start generator + foreach ($iterator as $item) { + // do nothing } + /** @see TupleStorage::$iterators */ + self::assertCount(0, (fn(): array => $this->iterators)->call($storage)); + } + + public function testDetachWhenIterating(): void + { + $storage = new TupleStorage(); + $tuple1 = $this->createTuple((object)['value' => 1]); + $tuple2 = $this->createTuple((object)['value' => 2]); + $tuple3 = $this->createTuple((object)['value' => 3]); + $tuple4 = $this->createTuple((object)['value' => 4]); + + $storage->attach($tuple1); + $storage->attach($tuple2); + $storage->attach($tuple3); + $storage->attach($tuple4); $collection = []; - $i = 0; - foreach ($storage as $entity => $tuple) { + foreach ($storage as $tuple) { $collection[] = $tuple; self::assertTrue($storage->contains($tuple->entity)); self::assertSame($tuple, $storage->getTuple($tuple->entity)); - // Add a new item after each 10th iteration - if (++$i % 10 === 0) { - $newTuple = $this->createTuple(new \stdClass()); - $storage->attach($newTuple); - $tuples[] = $newTuple; + if ($tuple === $tuple2) { + $storage->detach($tuple3->entity); } } + self::assertCount(3, $storage); + self::assertSame([$tuple1, $tuple2, $tuple4], $collection); + } - self::assertCount(\count($tuples), $storage); - self::assertCount(\count($tuples), $collection); - self::assertSame(\array_values($tuples), $collection); + public function testCleanupIteratorState(): void + { + $storage = new TupleStorage(); + $tuple1 = $this->createTuple((object)['value' => 1]); + $tuple2 = $this->createTuple((object)['value' => 2]); + $tuple3 = $this->createTuple((object)['value' => 3]); + $tuple4 = $this->createTuple((object)['value' => 4]); + + $storage->attach($tuple1); + $storage->attach($tuple2); + $storage->attach($tuple3); + $storage->attach($tuple4); + + $collection = []; + foreach ($storage as $tuple) { + $collection[] = $tuple; + self::assertTrue($storage->contains($tuple->entity)); + self::assertSame($tuple, $storage->getTuple($tuple->entity)); + + if ($tuple === $tuple2) { + $storage->detach($tuple3->entity); + } + } + self::assertCount(3, $storage); + self::assertSame([$tuple1, $tuple2, $tuple4], $collection); + } + + public function testParallelIterators(): void + { + $storage = new TupleStorage(); + for ($i = 0; $i < 5; $i++) { + $tuple = $this->createTuple(new \stdClass()); + $storage->attach($tuple); + } + + /** @var \Generator $iterator1 */ + $iterator1 = $storage->getIterator(); + + $i = 0; + foreach ($storage as $tuple) { + self::assertTrue($iterator1->valid()); + self::assertSame($tuple, $iterator1->current()); + + if (++$i % 2 === 0) { + $storage->attach($this->createTuple(new \stdClass())); + } + $iterator1->next(); + } } private function createTuple(object $entity): Tuple From 23db7705c709fe20223433bb2780f729bb0fdded Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 13 Feb 2024 14:05:22 +0400 Subject: [PATCH 10/10] Fix metafiles --- CONTRIBUTING.md | 8 ++++---- phpunit.xml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4bb3037f..a7f2c237 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,20 +13,20 @@ Please make sure that the following requirements are satisfied before submitting To test ORM engine locally, download the `cycle/orm` repository and start docker containers inside the tests folder: ```bash -$ cd tests/ -$ docker-compose up +cd tests/ +docker-compose up ``` To run full test suite: ```bash -$ ./vendor/bin/phpunit +./vendor/bin/phpunit ``` To run quick test suite: ```bash -$ ./vendor/bin/phpunit tests/ORM/Functional/Driver/SQLite +./vendor/bin/phpunit tests/ORM/Functional/Driver/SQLite ``` ## Help Needed In diff --git a/phpunit.xml b/phpunit.xml index 7ed98adb..bbc40674 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,8 +11,8 @@ convertWarningsToExceptions="true" convertDeprecationsToExceptions="true" processIsolation="false" - stopOnFailure="false" - stopOnError="false" + stopOnFailure="true" + stopOnError="true" >