diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 93da6f51..d8be5dfa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,7 +85,7 @@ jobs: image: mysql:8.0 ports: - 3307:3306 - options: --health-cmd="mysqladmin ping -ppass" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=nextras_orm_test --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + options: --health-cmd="mysqladmin ping -ppass" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=nextras_orm_test --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --mysql-native-password=ON" mariadb105: image: mariadb:10.5 env: @@ -135,7 +135,7 @@ jobs: - 1433:1433 options: >- --name=mssql - --health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" + --health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -145,7 +145,7 @@ jobs: uses: actions/checkout@v2 - name: Create MS SQL Database - run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE nextras_orm_test' + run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE nextras_orm_test' - name: Setup PHP cache environment id: php-extensions-cache diff --git a/src/Mapper/Dbal/DbalMapper.php b/src/Mapper/Dbal/DbalMapper.php index 76fe87fd..1c9dfc20 100644 --- a/src/Mapper/Dbal/DbalMapper.php +++ b/src/Mapper/Dbal/DbalMapper.php @@ -353,7 +353,10 @@ public function persist(IEntity $entity): void } else { $primary = []; - $id = (array) $entity->getPersistedId(); + $id = $entity->getPersistedId(); + if (!is_array($id)) { + $id = [$id]; + } foreach ($entity->getMetadata()->getPrimaryKey() as $key) { $primary[$key] = array_shift($id); } @@ -458,7 +461,10 @@ protected function processMySQLAutoupdate(IEntity $entity, array $args): void $conventions = $this->getConventions(); - $id = (array) $entity->getPersistedId(); + $id = $entity->getPersistedId(); + if (!is_array($id)) { + $id = [$id]; + } $primary = []; foreach ($entity->getMetadata()->getPrimaryKey() as $key) { $primary[$key] = array_shift($id); @@ -487,7 +493,10 @@ public function remove(IEntity $entity): void $conventions = $this->getConventions(); $primary = []; - $id = (array) $entity->getPersistedId(); + $id = $entity->getPersistedId(); + if (!is_array($id)) { + $id = [$id]; + } foreach ($entity->getMetadata()->getPrimaryKey() as $key) { $key = $conventions->convertEntityToStorageKey($key); $primary[$key] = array_shift($id); diff --git a/tests/cases/integration/Entity/entity.compositePK.phpt b/tests/cases/integration/Entity/entity.compositePK.phpt index dd3f41cb..f799d12c 100644 --- a/tests/cases/integration/Entity/entity.compositePK.phpt +++ b/tests/cases/integration/Entity/entity.compositePK.phpt @@ -9,13 +9,16 @@ namespace NextrasTests\Orm\Integration\Entity; use DateTimeImmutable; +use Nette\Utils\DateTime; use Nextras\Dbal\IConnection; use Nextras\Orm\Exception\InvalidArgumentException; use NextrasTests\Orm\DataTestCase; use NextrasTests\Orm\Helper; use NextrasTests\Orm\User; use NextrasTests\Orm\UserStat; +use NextrasTests\Orm\UserStatX; use Tester\Assert; +use Tester\Environment; require_once __DIR__ . '/../../../bootstrap.php'; @@ -46,19 +49,52 @@ class EntityCompositePKTest extends DataTestCase $this->orm->clear(); - $userStat = $this->orm->userStats->getBy(['user' => $userId, 'date' => $at]); - Assert::notNull($userStat); + $userStat = $this->orm->userStats->getByChecked(['user' => $userId, 'date' => $at]); Assert::type(DateTimeImmutable::class, $userStat->id[1]); $userStat->value = 101; $this->orm->persistAndFlush($userStat); } + public function testCompositePKDateTime2(): void + { + if ($this->section === Helper::SECTION_MSSQL) { + // An explicit value for the identity column in table 'users' can only be specified when a column list is used and IDENTITY_INSERT is ON. + // http://stackoverflow.com/questions/2148091/syntax-for-inserting-into-a-table-with-no-values + Environment::skip('Inserting dummy rows when no arguments are passed is not supported.'); + } + + $user = new User(); + $this->orm->persistAndFlush($user); + + $stat = new UserStatX(); + $stat->user = $user; + $stat->date = '2019-01-01'; + $stat->value = 100; + $this->orm->persistAndFlush($stat); + + $this->orm->clear(); + + $res = $this->orm->userStatsX->getByChecked(['date' => new DateTime('2019-01-01')]); + Assert::same(100, $res->value); + + $res->value = 200; + $this->orm->persistAndFlush($res); + Assert::same(200, $res->value); + + $this->orm->clear(); + + $res = $this->orm->userStatsX->getByChecked(['date' => new DateTime('2019-01-01')]); + Assert::same(200, $res->value); + + Environment::$checkAssertions = false; + } + + public function testGetBy(): void { - $tagFollower = $this->orm->tagFollowers->getBy(['tag' => 3, 'author' => 1]); - Assert::notNull($tagFollower); + $tagFollower = $this->orm->tagFollowers->getByChecked(['tag' => 3, 'author' => 1]); Assert::same($tagFollower->tag->name, 'Tag 3'); Assert::same($tagFollower->author->name, 'Writer 1'); diff --git a/tests/cases/integration/Entity/entity.pk.phpt b/tests/cases/integration/Entity/entity.pk.phpt index d474230f..a9215ca5 100644 --- a/tests/cases/integration/Entity/entity.pk.phpt +++ b/tests/cases/integration/Entity/entity.pk.phpt @@ -11,6 +11,7 @@ namespace NextrasTests\Orm\Integration\Entity; use DateTimeImmutable; use NextrasTests\Orm\DataTestCase; use NextrasTests\Orm\Log; +use NextrasTests\Orm\TimeSeries; use Tester\Assert; @@ -32,6 +33,22 @@ class EntityPkTest extends DataTestCase $entry = $this->orm->logs->getById($datetime); Assert::true($entry !== null); } + + public function testDateTimeWithProxyPkUpdate(): void + { + $timeSeries = new TimeSeries(); + $timeSeries->id = $datetime = new DateTimeImmutable('2022-03-06T03:03:03Z'); + $timeSeries->value = 3; + $this->orm->persistAndFlush($timeSeries); + + $this->orm->clear(); + $timeSeries = $this->orm->timeSeries->getByIdChecked($datetime); + $timeSeries->value = 5; + $this->orm->persistAndFlush($timeSeries); + + $entry = $this->orm->timeSeries->getById($datetime); + Assert::true($entry !== null); + } } diff --git a/tests/db/mssql-init.sql b/tests/db/mssql-init.sql index d47dd5c5..8b89ab23 100644 --- a/tests/db/mssql-init.sql +++ b/tests/db/mssql-init.sql @@ -141,6 +141,15 @@ CREATE TABLE user_stats CONSTRAINT user_stats_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE NO ACTION ON UPDATE CASCADE ); +CREATE TABLE user_stats_x +( + user_id int NOT NULL, + date date NOT NULL, + value int NOT NULL, + PRIMARY KEY (user_id, date), + CONSTRAINT user_stats_x_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE NO ACTION ON UPDATE CASCADE +); + CREATE TABLE users_x_users ( @@ -176,3 +185,11 @@ CREATE TABLE publishers_x_tags CONSTRAINT publishers_x_tags_tag FOREIGN KEY (tag_id) REFERENCES tags (id), CONSTRAINT publishers_x_tags_publisher FOREIGN KEY (publisher_id) REFERENCES publishers (publisher_id) ON DELETE CASCADE ); + + +CREATE TABLE time_series +( + date datetimeoffset NOT NULL, + value int NOT NULL, + PRIMARY KEY (date) +); diff --git a/tests/db/mysql-init.sql b/tests/db/mysql-init.sql index 2ae9e55c..a0295493 100644 --- a/tests/db/mysql-init.sql +++ b/tests/db/mysql-init.sql @@ -121,6 +121,12 @@ CREATE TABLE photos ) AUTO_INCREMENT = 1; +CREATE TRIGGER `book_collections_bu_trigger` BEFORE UPDATE ON `book_collections` +FOR EACH ROW SET NEW.updated_at = NOW(); + +CREATE TRIGGER `book_collections_bi_trigger` BEFORE INSERT ON `book_collections` +FOR EACH ROW SET NEW.updated_at = NOW(); + ALTER TABLE photo_albums ADD CONSTRAINT photo_albums_preview_id FOREIGN KEY (preview_id) REFERENCES photos (id) ON DELETE CASCADE ON UPDATE CASCADE; @@ -141,6 +147,13 @@ CREATE TABLE user_stats CONSTRAINT user_stats_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE ); +CREATE TABLE user_stats_x ( + user_id int NOT NULL, + date date NOT NULL, + value int NOT NULL, + PRIMARY KEY(user_id, date), + CONSTRAINT user_stats_x_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE +); CREATE TABLE users_x_users ( @@ -152,17 +165,6 @@ CREATE TABLE users_x_users ); -CREATE TRIGGER `book_collections_bu_trigger` - BEFORE UPDATE - ON `book_collections` - FOR EACH ROW SET NEW.updated_at = NOW(); - -CREATE TRIGGER `book_collections_bi_trigger` - BEFORE INSERT - ON `book_collections` - FOR EACH ROW SET NEW.updated_at = NOW(); - - CREATE TABLE logs ( date TIMESTAMP NOT NULL, @@ -179,3 +181,10 @@ CREATE TABLE publishers_x_tags CONSTRAINT publishers_x_tags_tag FOREIGN KEY (tag_id) REFERENCES tags (id), CONSTRAINT publishers_x_tags_publisher FOREIGN KEY (publisher_id) REFERENCES publishers (publisher_id) ON DELETE CASCADE ); + +CREATE TABLE time_series +( + date DATETIME NOT NULL, + value int NOT NULL, + PRIMARY KEY (date) +) diff --git a/tests/db/pgsql-init.sql b/tests/db/pgsql-init.sql index 16e253ae..c5f72db4 100644 --- a/tests/db/pgsql-init.sql +++ b/tests/db/pgsql-init.sql @@ -120,7 +120,6 @@ CREATE TABLE "photos" CONSTRAINT "photos_album_id" FOREIGN KEY ("album_id") REFERENCES "photo_albums" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); - ALTER TABLE "photo_albums" ADD CONSTRAINT "photo_albums_preview_id" FOREIGN KEY ("preview_id") REFERENCES "photos" ("id") ON DELETE CASCADE ON UPDATE CASCADE; @@ -141,6 +140,15 @@ CREATE TABLE "user_stats" CONSTRAINT "user_stats_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); +CREATE TABLE "user_stats_x" +( + "user_id" int NOT NULL, + "date" date NOT NULL, + "value" int NOT NULL, + PRIMARY KEY ("user_id", "date"), + CONSTRAINT "user_stats_x_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + CREATE TABLE "users_x_users" ( @@ -190,3 +198,11 @@ CREATE TABLE "publishers_x_tags" CONSTRAINT "publishers_x_tags_tag" FOREIGN KEY ("tag_id") REFERENCES "tags" ("id"), CONSTRAINT "publishers_x_tags_publisher" FOREIGN KEY ("publisher_id") REFERENCES "publishers" ("publisher_id") ON DELETE CASCADE ON UPDATE CASCADE ); + + +CREATE TABLE "time_series" +( + "date" TIMESTAMPTZ NOT NULL, + "value" int NOT NULL, + PRIMARY KEY ("date") +); diff --git a/tests/inc/model/Model.php b/tests/inc/model/Model.php index cfe2f209..fc548a50 100644 --- a/tests/inc/model/Model.php +++ b/tests/inc/model/Model.php @@ -21,6 +21,8 @@ * @property-read TagFollowersRepository $tagFollowers * @property-read UsersRepository $users * @property-read UserStatsRepository $userStats + * @property-read UserStatsXRepository $userStatsX + * @property-read TimeSeriesRepository $timeSeries */ class Model extends OrmModel { diff --git a/tests/inc/model/timeSeries/TimeSeries.php b/tests/inc/model/timeSeries/TimeSeries.php new file mode 100644 index 00000000..14a74999 --- /dev/null +++ b/tests/inc/model/timeSeries/TimeSeries.php @@ -0,0 +1,17 @@ + + */ +final class TimeSeriesMapper extends DbalMapper +{ +} diff --git a/tests/inc/model/timeSeries/TimeSeriesRepository.php b/tests/inc/model/timeSeries/TimeSeriesRepository.php new file mode 100644 index 00000000..f3db3efd --- /dev/null +++ b/tests/inc/model/timeSeries/TimeSeriesRepository.php @@ -0,0 +1,18 @@ + + */ +final class TimeSeriesRepository extends Repository +{ + static function getEntityClassNames(): array + { + return [TimeSeries::class]; + } +} diff --git a/tests/inc/model/userStatX/UserStatX.php b/tests/inc/model/userStatX/UserStatX.php new file mode 100644 index 00000000..27166d64 --- /dev/null +++ b/tests/inc/model/userStatX/UserStatX.php @@ -0,0 +1,17 @@ + + */ +final class UserStatsXMapper extends DbalMapper +{ +} diff --git a/tests/inc/model/userStatX/UserStatsXRepository.php b/tests/inc/model/userStatX/UserStatsXRepository.php new file mode 100644 index 00000000..87acc7cf --- /dev/null +++ b/tests/inc/model/userStatX/UserStatsXRepository.php @@ -0,0 +1,17 @@ + + */ +final class UserStatsXRepository extends Repository +{ + static function getEntityClassNames(): array + { + return [UserStatX::class]; + } +} diff --git a/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityCompositePKTest_testCompositePKDateTime2.sql b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityCompositePKTest_testCompositePKDateTime2.sql new file mode 100644 index 00000000..2a1f742b --- /dev/null +++ b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityCompositePKTest_testCompositePKDateTime2.sql @@ -0,0 +1,12 @@ +START TRANSACTION; +INSERT INTO "users" VALUES (DEFAULT); +SELECT CURRVAL('"users_id_seq"'); +COMMIT; +START TRANSACTION; +INSERT INTO "user_stats_x" ("user_id", "date", "value") VALUES (1, '2019-01-01 00:00:00.000000'::timestamp, 100); +COMMIT; +SELECT "user_stats_x".* FROM "user_stats_x" AS "user_stats_x" WHERE (("user_stats_x"."date" = '2019-01-01 00:00:00.000000'::timestamp)); +START TRANSACTION; +UPDATE "user_stats_x" SET "value" = 200 WHERE "user_id" = 1 AND "date" = '2019-01-01 00:00:00.000000'::timestamp; +COMMIT; +SELECT "user_stats_x".* FROM "user_stats_x" AS "user_stats_x" WHERE (("user_stats_x"."date" = '2019-01-01 00:00:00.000000'::timestamp)); diff --git a/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPk.sql b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPk.sql index 8ffa6c23..c142dfda 100644 --- a/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPk.sql +++ b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPk.sql @@ -2,5 +2,5 @@ START TRANSACTION; INSERT INTO "logs" ("date", "count") VALUES ('2022-03-06 03:03:03.000000'::timestamptz, 3); COMMIT; START TRANSACTION; -UPDATE "logs" SET "count" = 5 WHERE "date" = '2022-03-06 03:03:03.000000'; +UPDATE "logs" SET "count" = 5 WHERE "date" = '2022-03-06 03:03:03.000000'::timestamptz; COMMIT; diff --git a/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPkUpdate.sql b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPkUpdate.sql new file mode 100644 index 00000000..65e33d8a --- /dev/null +++ b/tests/sqls/NextrasTests/Orm/Integration/Entity/EntityPkTest_testDateTimeWithProxyPkUpdate.sql @@ -0,0 +1,8 @@ +START TRANSACTION; +INSERT INTO "time_series" ("date", "value") VALUES ('2022-03-06 03:03:03.000000'::timestamptz, 3); +COMMIT; +SELECT "time_series".* FROM "time_series" AS "time_series" WHERE (("time_series"."date" = '2022-03-06 03:03:03.000000'::timestamptz)); +START TRANSACTION; +UPDATE "time_series" SET "value" = 5 WHERE "date" = '2022-03-06 03:03:03.000000'::timestamptz; +COMMIT; +SELECT "time_series".* FROM "time_series" AS "time_series" WHERE (("time_series"."date" = '2022-03-06 03:03:03.000000'::timestamptz));