From d202ecf06f343498630728a772132102eed0ef13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 6 Jun 2024 22:38:33 +0200 Subject: [PATCH 01/56] Added support of the custom TinyInt type for postgres --- src/Doctrine/Types/TinyIntType.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index e9a75daa..70a02e14 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -22,7 +22,9 @@ declare(strict_types=1); */ namespace App\Doctrine\Types; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Types\Type; /** @@ -33,7 +35,13 @@ class TinyIntType extends Type public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return 'TINYINT'; + //MySQL and SQLite know the TINYINT type directly + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SqlitePlatform) { + return 'TINYINT'; + } + + //For other platforms, we use the smallest integer type available + return $platform->getSmallIntTypeDeclarationSQL($column); } public function getName(): string From 02acafc348f05420cf42be717024c492aaa5c88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 6 Jun 2024 23:11:11 +0200 Subject: [PATCH 02/56] Added postgres to the AbstractMultiPlatformMigration class --- migrations/Version1.php | 10 + migrations/Version20190902140506.php | 10 + migrations/Version20190913141126.php | 10 + migrations/Version20190924113252.php | 10 + migrations/Version20191214153125.php | 10 + migrations/Version20200126191823.php | 10 + migrations/Version20200311204104.php | 10 + migrations/Version20200409130946.php | 10 + migrations/Version20200502161750.php | 10 + migrations/Version20220925162725.php | 10 + migrations/Version20221003212851.php | 10 + migrations/Version20221114193325.php | 10 + migrations/Version20221204004815.php | 10 + migrations/Version20221216224745.php | 9 + migrations/Version20230108165410.php | 10 + migrations/Version20230219225340.php | 10 + migrations/Version20230220221024.php | 10 + migrations/Version20230402170923.php | 10 + migrations/Version20230408170059.php | 10 + migrations/Version20230408213957.php | 10 + migrations/Version20230417211732.php | 10 + migrations/Version20230528000149.php | 10 + migrations/Version20230716184033.php | 10 + migrations/Version20230730131708.php | 10 + migrations/Version20230816213201.php | 10 + migrations/Version20231114223101.php | 10 + migrations/Version20231130180903.php | 10 + migrations/Version20240427222442.php | 10 + migrations/Version20240606203053.php | 365 ++++++++++++++++++ .../AbstractMultiPlatformMigration.php | 11 + 30 files changed, 655 insertions(+) create mode 100644 migrations/Version20240606203053.php diff --git a/migrations/Version1.php b/migrations/Version1.php index 59f40c74..fc4c7b2e 100644 --- a/migrations/Version1.php +++ b/migrations/Version1.php @@ -235,4 +235,14 @@ EOD; { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190902140506.php b/migrations/Version20190902140506.php index 36f184d5..8984cb06 100644 --- a/migrations/Version20190902140506.php +++ b/migrations/Version20190902140506.php @@ -380,4 +380,14 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190913141126.php b/migrations/Version20190913141126.php index 58093338..31eed64a 100644 --- a/migrations/Version20190913141126.php +++ b/migrations/Version20190913141126.php @@ -88,4 +88,14 @@ final class Version20190913141126 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190924113252.php b/migrations/Version20190924113252.php index fe7c9c8b..8221c686 100644 --- a/migrations/Version20190924113252.php +++ b/migrations/Version20190924113252.php @@ -179,4 +179,14 @@ final class Version20190924113252 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20191214153125.php b/migrations/Version20191214153125.php index 9a61c10d..83803d13 100644 --- a/migrations/Version20191214153125.php +++ b/migrations/Version20191214153125.php @@ -65,4 +65,14 @@ final class Version20191214153125 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200126191823.php b/migrations/Version20200126191823.php index c093a4d7..cf362ea5 100644 --- a/migrations/Version20200126191823.php +++ b/migrations/Version20200126191823.php @@ -68,4 +68,14 @@ final class Version20200126191823 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200311204104.php b/migrations/Version20200311204104.php index 2a90b62c..daa6fd28 100644 --- a/migrations/Version20200311204104.php +++ b/migrations/Version20200311204104.php @@ -56,4 +56,14 @@ final class Version20200311204104 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200409130946.php b/migrations/Version20200409130946.php index 72bdda9c..ef2dc7ab 100644 --- a/migrations/Version20200409130946.php +++ b/migrations/Version20200409130946.php @@ -42,4 +42,14 @@ final class Version20200409130946 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200502161750.php b/migrations/Version20200502161750.php index 93bb0d00..55117541 100644 --- a/migrations/Version20200502161750.php +++ b/migrations/Version20200502161750.php @@ -163,4 +163,14 @@ EOD; $this->addSql('DROP TABLE u2f_keys'); $this->addSql('DROP TABLE "users"'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20220925162725.php b/migrations/Version20220925162725.php index 0f04f04e..21ac8946 100644 --- a/migrations/Version20220925162725.php +++ b/migrations/Version20220925162725.php @@ -535,4 +535,14 @@ final class Version20220925162725 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); $this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221003212851.php b/migrations/Version20221003212851.php index 3a7379ca..81e33c0e 100644 --- a/migrations/Version20221003212851.php +++ b/migrations/Version20221003212851.php @@ -47,4 +47,14 @@ final class Version20221003212851 extends AbstractMultiPlatformMigration { $this->addSql('DROP TABLE webauthn_keys'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221114193325.php b/migrations/Version20221114193325.php index 9075dc7a..2775ab5e 100644 --- a/migrations/Version20221114193325.php +++ b/migrations/Version20221114193325.php @@ -171,4 +171,14 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); } } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221204004815.php b/migrations/Version20221204004815.php index 20e151db..c6c7b6ab 100644 --- a/migrations/Version20221204004815.php +++ b/migrations/Version20221204004815.php @@ -55,4 +55,14 @@ final class Version20221204004815 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221216224745.php b/migrations/Version20221216224745.php index 63bb535e..9ac3b8ba 100644 --- a/migrations/Version20221216224745.php +++ b/migrations/Version20221216224745.php @@ -67,6 +67,15 @@ final class Version20221216224745 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX log_idx_type ON log (type)'); $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); } } diff --git a/migrations/Version20230108165410.php b/migrations/Version20230108165410.php index 4124a95a..90f6314e 100644 --- a/migrations/Version20230108165410.php +++ b/migrations/Version20230108165410.php @@ -320,4 +320,14 @@ final class Version20230108165410 extends AbstractMultiPlatformMigration $this->addSql('ALTER TABLE projects RENAME TO devices'); $this->addSql('ALTER TABLE project_bom_entries RENAME TO device_parts'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230219225340.php b/migrations/Version20230219225340.php index 94e08963..2740c85e 100644 --- a/migrations/Version20230219225340.php +++ b/migrations/Version20230219225340.php @@ -521,4 +521,14 @@ final class Version20230219225340 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + } diff --git a/migrations/Version20230220221024.php b/migrations/Version20230220221024.php index 01d65d51..b2209351 100644 --- a/migrations/Version20230220221024.php +++ b/migrations/Version20230220221024.php @@ -37,4 +37,14 @@ final class Version20230220221024 extends AbstractMultiPlatformMigration { $this->addSql('ALTER TABLE `users` DROP saml_user'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230402170923.php b/migrations/Version20230402170923.php index 016a10d0..3325afb6 100644 --- a/migrations/Version20230402170923.php +++ b/migrations/Version20230402170923.php @@ -297,4 +297,14 @@ final class Version20230402170923 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408170059.php b/migrations/Version20230408170059.php index 88be7798..e3662e16 100644 --- a/migrations/Version20230408170059.php +++ b/migrations/Version20230408170059.php @@ -49,4 +49,14 @@ final class Version20230408170059 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408213957.php b/migrations/Version20230408213957.php index 976a79db..47807126 100644 --- a/migrations/Version20230408213957.php +++ b/migrations/Version20230408213957.php @@ -434,4 +434,14 @@ final class Version20230408213957 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230417211732.php b/migrations/Version20230417211732.php index 7fef77eb..5fa3b5f7 100644 --- a/migrations/Version20230417211732.php +++ b/migrations/Version20230417211732.php @@ -45,4 +45,14 @@ final class Version20230417211732 extends AbstractMultiPlatformMigration { //As we done nothing, we don't need to implement this method. } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230528000149.php b/migrations/Version20230528000149.php index 05830937..5e742383 100644 --- a/migrations/Version20230528000149.php +++ b/migrations/Version20230528000149.php @@ -62,4 +62,14 @@ final class Version20230528000149 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230716184033.php b/migrations/Version20230716184033.php index adf3e83c..7c0d8a12 100644 --- a/migrations/Version20230716184033.php +++ b/migrations/Version20230716184033.php @@ -348,4 +348,14 @@ final class Version20230716184033 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230730131708.php b/migrations/Version20230730131708.php index 88b49460..a12c3b8d 100644 --- a/migrations/Version20230730131708.php +++ b/migrations/Version20230730131708.php @@ -99,4 +99,14 @@ final class Version20230730131708 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230816213201.php b/migrations/Version20230816213201.php index 66f17d8a..d776da26 100644 --- a/migrations/Version20230816213201.php +++ b/migrations/Version20230816213201.php @@ -41,4 +41,14 @@ final class Version20230816213201 extends AbstractMultiPlatformMigration { $this->addSql('DROP TABLE api_tokens'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20231114223101.php b/migrations/Version20231114223101.php index 3a8f3b02..c06cb279 100644 --- a/migrations/Version20231114223101.php +++ b/migrations/Version20231114223101.php @@ -68,4 +68,14 @@ final class Version20231114223101 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20231130180903.php b/migrations/Version20231130180903.php index 8d00065c..0eccb5aa 100644 --- a/migrations/Version20231130180903.php +++ b/migrations/Version20231130180903.php @@ -85,4 +85,14 @@ final class Version20231130180903 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20240427222442.php b/migrations/Version20240427222442.php index f9471ff1..92b2733d 100644 --- a/migrations/Version20240427222442.php +++ b/migrations/Version20240427222442.php @@ -62,4 +62,14 @@ final class Version20240427222442 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php new file mode 100644 index 00000000..b305f739 --- /dev/null +++ b/migrations/Version20240606203053.php @@ -0,0 +1,365 @@ +addSql('CREATE SEQUENCE api_tokens_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "attachment_types_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "attachments_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "categories_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE currencies_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "footprints_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "groups_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE label_profiles_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE log_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "manufacturers_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "measurement_units_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE oauth_tokens_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "orderdetails_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE parameters_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE part_association_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE part_lots_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "parts_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "pricedetails_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE project_bom_entries_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE projects_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "storelocations_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "suppliers_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE u2f_keys_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "users_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE webauthn_keys_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE api_tokens (id INT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); + $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); + $this->addSql('CREATE TABLE "attachment_types" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, filetype_filter TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)'); + $this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)'); + $this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)'); + $this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)'); + $this->addSql('CREATE TABLE "attachments" (id INT NOT NULL, type_id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, path VARCHAR(255) NOT NULL, show_in_table BOOLEAN NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)'); + $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)'); + $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)'); + $this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)'); + $this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)'); + $this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)'); + $this->addSql('CREATE TABLE "categories" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, partname_hint TEXT NOT NULL, partname_regex TEXT NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description TEXT NOT NULL, default_comment TEXT NOT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)'); + $this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)'); + $this->addSql('CREATE INDEX category_idx_name ON "categories" (name)'); + $this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)'); + $this->addSql('CREATE TABLE currencies (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('COMMENT ON COLUMN currencies.exchange_rate IS \'(DC2Type:big_decimal)\''); + $this->addSql('CREATE TABLE "footprints" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_footprint_3d INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)'); + $this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)'); + $this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)'); + $this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)'); + $this->addSql('CREATE TABLE "groups" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TABLE label_profiles (id INT NOT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, show_in_dropdown BOOLEAN NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css TEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)'); + $this->addSql('CREATE TABLE log (id INT NOT NULL, id_user INT DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, level SMALLINT NOT NULL, target_id INT NOT NULL, target_type SMALLINT NOT NULL, extra JSON NOT NULL, type SMALLINT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('COMMENT ON COLUMN log.level IS \'(DC2Type:tinyint)\''); + $this->addSql('CREATE TABLE "manufacturers" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)'); + $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)'); + $this->addSql('CREATE TABLE "measurement_units" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)'); + $this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)'); + $this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)'); + $this->addSql('CREATE TABLE oauth_tokens (id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, token TEXT DEFAULT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refresh_token TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('COMMENT ON COLUMN oauth_tokens.expires_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE "orderdetails" (id INT NOT NULL, part_id INT NOT NULL, id_supplier INT DEFAULT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url TEXT NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)'); + $this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)'); + $this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)'); + $this->addSql('CREATE TABLE parameters (id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, symbol VARCHAR(255) NOT NULL, value_min DOUBLE PRECISION DEFAULT NULL, value_typical DOUBLE PRECISION DEFAULT NULL, value_max DOUBLE PRECISION DEFAULT NULL, unit VARCHAR(255) NOT NULL, value_text VARCHAR(255) NOT NULL, param_group VARCHAR(255) NOT NULL, type SMALLINT NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_69348FE1F1F2A24 ON parameters (element_id)'); + $this->addSql('CREATE INDEX parameter_name_idx ON parameters (name)'); + $this->addSql('CREATE INDEX parameter_group_idx ON parameters (param_group)'); + $this->addSql('CREATE INDEX parameter_type_element_idx ON parameters (type, element_id)'); + $this->addSql('CREATE TABLE part_association (id INT NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)'); + $this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)'); + $this->addSql('CREATE TABLE part_lots (id INT NOT NULL, id_store_location INT DEFAULT NULL, id_part INT NOT NULL, id_owner INT DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); + $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); + $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); + $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); + $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); + $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); + $this->addSql('CREATE TABLE "parts" (id INT NOT NULL, id_preview_attachment INT DEFAULT NULL, id_category INT NOT NULL, id_footprint INT DEFAULT NULL, id_part_unit INT DEFAULT NULL, id_manufacturer INT DEFAULT NULL, order_orderdetails_id INT DEFAULT NULL, built_project_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags TEXT NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url TEXT NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INT NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); + $this->addSql('CREATE TABLE "pricedetails" (id INT NOT NULL, id_currency INT DEFAULT NULL, orderdetails_id INT NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('COMMENT ON COLUMN "pricedetails".price IS \'(DC2Type:big_decimal)\''); + $this->addSql('CREATE TABLE project_bom_entries (id INT NOT NULL, id_device INT DEFAULT NULL, id_part INT DEFAULT NULL, price_currency_id INT DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames TEXT NOT NULL, name VARCHAR(255) DEFAULT NULL, comment TEXT NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('COMMENT ON COLUMN project_bom_entries.price IS \'(DC2Type:big_decimal)\''); + $this->addSql('CREATE TABLE projects (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, order_quantity INT NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)'); + $this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)'); + $this->addSql('CREATE TABLE "storelocations" (id INT NOT NULL, parent_id INT DEFAULT NULL, storage_type_id INT DEFAULT NULL, id_owner INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)'); + $this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)'); + $this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)'); + $this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)'); + $this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)'); + $this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)'); + $this->addSql('CREATE TABLE "suppliers" (id INT NOT NULL, parent_id INT DEFAULT NULL, default_currency_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('COMMENT ON COLUMN "suppliers".shipping_costs IS \'(DC2Type:big_decimal)\''); + $this->addSql('CREATE TABLE u2f_keys (id INT NOT NULL, user_id INT DEFAULT NULL, key_handle VARCHAR(128) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate TEXT NOT NULL, counter VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_4F4ADB4BA76ED395 ON u2f_keys (user_id)'); + $this->addSql('CREATE UNIQUE INDEX user_unique ON u2f_keys (user_id, key_handle)'); + $this->addSql('CREATE TABLE "users" (id INT NOT NULL, group_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, currency_id INT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a TEXT NOT NULL, config_instock_comment_w TEXT NOT NULL, about_me TEXT NOT NULL, trusted_device_cookie_version INT NOT NULL, backup_codes JSON NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT false NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings JSON NOT NULL, backup_codes_generation_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, pw_reset_expires TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TABLE webauthn_keys (id INT NOT NULL, user_id INT DEFAULT NULL, public_key_credential_id TEXT NOT NULL, type VARCHAR(255) NOT NULL, transports TEXT NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path JSON NOT NULL, aaguid TEXT NOT NULL, credential_public_key TEXT NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INT NOT NULL, other_ui TEXT DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, name VARCHAR(255) NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + $this->addSql('COMMENT ON COLUMN webauthn_keys.public_key_credential_id IS \'(DC2Type:base64)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.transports IS \'(DC2Type:array)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.trust_path IS \'(DC2Type:trust_path)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.aaguid IS \'(DC2Type:aaguid)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.credential_public_key IS \'(DC2Type:base64)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.other_ui IS \'(DC2Type:array)\''); + $this->addSql('COMMENT ON COLUMN webauthn_keys.last_time_used IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachments" ADD CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF5EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE log ADD CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE u2f_keys ADD CONSTRAINT FK_4F4ADB4BA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function postgreSQLDown(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE api_tokens_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "attachment_types_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "attachments_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "categories_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE currencies_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "footprints_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "groups_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE label_profiles_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE log_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "manufacturers_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "measurement_units_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE oauth_tokens_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "orderdetails_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE parameters_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE part_association_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE part_lots_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "parts_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "pricedetails_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE project_bom_entries_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE projects_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "storelocations_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE "suppliers_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE u2f_keys_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "users_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE webauthn_keys_id_seq CASCADE'); + $this->addSql('ALTER TABLE api_tokens DROP CONSTRAINT FK_2CAD560EA76ED395'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719727ACA70'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719EA7100A1'); + $this->addSql('ALTER TABLE "attachments" DROP CONSTRAINT FK_47C4FAD6C54C8C93'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668727ACA70'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668EA7100A1'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693727ACA70'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2727ACA70'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A232A38C34'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970727ACA70'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970EA7100A1'); + $this->addSql('ALTER TABLE label_profiles DROP CONSTRAINT FK_C93E9CF5EA7100A1'); + $this->addSql('ALTER TABLE log DROP CONSTRAINT FK_8F3F68C56B3CA4B'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12727ACA70'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12EA7100A1'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CF727ACA70'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CFEA7100A1'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDC4CE34BEC'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDCCBF180EB'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E07E3C61F9'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E0998D9879'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F9435D8F4B37'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F943C22F6CC4'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F94321E5A74C'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEEA7100A1'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE5697F554'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE7E371A10'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE2626CEF9'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE1ECB93AE'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE81081E9B'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEE8AE70D9'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C4459398D64AA'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C44594A01DDC7'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD312F180363'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD31C22F6CC4'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD313FFDCD60'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4727ACA70'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4EA7100A1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020727ACA70'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020B270BFF1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_751702021E5A74C'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020EA7100A1'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95C727ACA70'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CECD792C0'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CEA7100A1'); + $this->addSql('ALTER TABLE u2f_keys DROP CONSTRAINT FK_4F4ADB4BA76ED395'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9FE54D947'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9EA7100A1'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E938248176'); + $this->addSql('ALTER TABLE webauthn_keys DROP CONSTRAINT FK_799FD143A76ED395'); + $this->addSql('DROP TABLE api_tokens'); + $this->addSql('DROP TABLE "attachment_types"'); + $this->addSql('DROP TABLE "attachments"'); + $this->addSql('DROP TABLE "categories"'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('DROP TABLE "footprints"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('DROP TABLE label_profiles'); + $this->addSql('DROP TABLE log'); + $this->addSql('DROP TABLE "manufacturers"'); + $this->addSql('DROP TABLE "measurement_units"'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('DROP TABLE "orderdetails"'); + $this->addSql('DROP TABLE parameters'); + $this->addSql('DROP TABLE part_association'); + $this->addSql('DROP TABLE part_lots'); + $this->addSql('DROP TABLE "parts"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('DROP TABLE projects'); + $this->addSql('DROP TABLE "storelocations"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('DROP TABLE u2f_keys'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('DROP TABLE webauthn_keys'); + } + + public function mySQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for MySQL"); + } + + public function mySQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for MySQL"); + } + + public function sqLiteUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Sqlite"); + } + + public function sqLiteDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Sqlite"); + } +} diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index 82b0fa73..bdad175f 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -25,6 +25,7 @@ namespace App\Migration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -50,6 +51,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLUp($schema), 'sqlite' => $this->sqLiteUp($schema), + 'postgresql' => $this->postgreSQLUp($schema), default => $this->abortIf(true, "Database type '$db_type' is not supported!"), }; } @@ -61,6 +63,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLDown($schema), 'sqlite' => $this->sqLiteDown($schema), + 'postgresql' => $this->postgreSQLDown($schema), default => $this->abortIf(true, "Database type is not supported!"), }; } @@ -167,6 +170,10 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -177,4 +184,8 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration abstract public function sqLiteUp(Schema $schema): void; abstract public function sqLiteDown(Schema $schema): void; + + abstract public function postgreSQLUp(Schema $schema): void; + + abstract public function postgreSQLDown(Schema $schema): void; } From a88a2e04cf31f5451bcc08b8a52ed9cdc41a8515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 00:11:58 +0200 Subject: [PATCH 03/56] Added the required initial users and groups for the database migration --- migrations/Version20221114193325.php | 44 ++-------------- migrations/Version20240606203053.php | 37 ++++++++++++- src/Migration/WithPermPresetsTrait.php | 72 ++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 43 deletions(-) create mode 100644 src/Migration/WithPermPresetsTrait.php diff --git a/migrations/Version20221114193325.php b/migrations/Version20221114193325.php index 2775ab5e..9766ccf3 100644 --- a/migrations/Version20221114193325.php +++ b/migrations/Version20221114193325.php @@ -4,24 +4,20 @@ declare(strict_types=1); namespace DoctrineMigrations; -use App\Entity\UserSystem\PermissionData; use App\Migration\AbstractMultiPlatformMigration; -use App\Security\Interfaces\HasPermissionsInterface; +use App\Migration\WithPermPresetsTrait; use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface { - private ?ContainerInterface $container = null; - private ?PermissionPresetsHelper $permission_presets_helper = null; + use WithPermPresetsTrait; public function __construct(Connection $connection, LoggerInterface $logger) { @@ -33,34 +29,6 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme return 'Update the permission system to the new system. Please note that all permissions will be reset!'; } - private function getJSONPermDataFromPreset(string $preset): string - { - if ($this->permission_presets_helper === null) { - throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); - } - - //Create a virtual user on which we can apply the preset - $user = new class implements HasPermissionsInterface { - - public PermissionData $perm_data; - - public function __construct() - { - $this->perm_data = new PermissionData(); - } - - public function getPermissions(): PermissionData - { - return $this->perm_data; - } - }; - - //Apply the preset to the virtual user - $this->permission_presets_helper->applyPreset($user, $preset); - - //And return the json data - return json_encode($user->getPermissions()); - } private function addDataMigrationAndWarning(): void { @@ -164,13 +132,7 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } - public function setContainer(ContainerInterface $container = null) - { - if ($container) { - $this->container = $container; - $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); - } - } + public function postgreSQLUp(Schema $schema): void { diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index b305f739..326f7aa2 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -5,14 +5,18 @@ declare(strict_types=1); namespace DoctrineMigrations; use App\Migration\AbstractMultiPlatformMigration; +use App\Migration\WithPermPresetsTrait; +use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20240606203053 extends AbstractMultiPlatformMigration +final class Version20240606203053 extends AbstractMultiPlatformMigration implements ContainerAwareInterface { + use WithPermPresetsTrait; + public function getDescription(): string { return 'Initial schema for Postgres'; @@ -233,6 +237,35 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + //Create the initial groups and users + //Retrieve the json representations of the presets + $admin = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_ADMIN); + $editor = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_EDITOR); + $read_only = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_READ_ONLY); + + + $sql = <<addSql($sql); + + $admin_pw = $this->getInitalAdminPW(); + + $sql = <<addSql($sql); } public function postgreSQLDown(Schema $schema): void diff --git a/src/Migration/WithPermPresetsTrait.php b/src/Migration/WithPermPresetsTrait.php new file mode 100644 index 00000000..debbade8 --- /dev/null +++ b/src/Migration/WithPermPresetsTrait.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Migration; + +use App\Entity\UserSystem\PermissionData; +use App\Security\Interfaces\HasPermissionsInterface; +use App\Services\UserSystem\PermissionPresetsHelper; +use Symfony\Component\DependencyInjection\ContainerInterface; + +trait WithPermPresetsTrait +{ + private ?ContainerInterface $container = null; + private ?PermissionPresetsHelper $permission_presets_helper = null; + + private function getJSONPermDataFromPreset(string $preset): string + { + if ($this->permission_presets_helper === null) { + throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); + } + + //Create a virtual user on which we can apply the preset + $user = new class implements HasPermissionsInterface { + + public PermissionData $perm_data; + + public function __construct() + { + $this->perm_data = new PermissionData(); + } + + public function getPermissions(): PermissionData + { + return $this->perm_data; + } + }; + + //Apply the preset to the virtual user + $this->permission_presets_helper->applyPreset($user, $preset); + + //And return the json data + return json_encode($user->getPermissions()); + } + + public function setContainer(ContainerInterface $container = null): void + { + if ($container) { + $this->container = $container; + $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); + } + } +} \ No newline at end of file From dc14b58d7398ac7502eeff9ad99f92102b32392a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 00:46:23 +0200 Subject: [PATCH 04/56] Fixed DBInfoHelper compatibility with postgres --- src/Services/Misc/DBInfoHelper.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Misc/DBInfoHelper.php index 65596b29..b093fda8 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Misc/DBInfoHelper.php @@ -25,6 +25,7 @@ namespace App\Services\Misc; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\ORM\EntityManagerInterface; @@ -54,6 +55,10 @@ class DBInfoHelper return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -71,6 +76,10 @@ class DBInfoHelper return $this->connection->fetchOne('SELECT sqlite_version()'); } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return $this->connection->fetchOne('SELECT version()'); + } + return null; } @@ -97,6 +106,14 @@ class DBInfoHelper } } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return (int) $this->connection->fetchOne('SELECT pg_database_size(current_database())'); + } catch (Exception) { + return null; + } + } + return null; } @@ -124,6 +141,14 @@ class DBInfoHelper if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { return 'sqlite'; } + + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return $this->connection->fetchOne('SELECT current_user'); + } catch (Exception) { + return null; + } + } return null; } From 205d5f8f58655fd1052bee31506795cf53260628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:11:08 +0200 Subject: [PATCH 05/56] Updated doctrine dbal and orm to next major version --- composer.json | 5 +- composer.lock | 331 +++++++++++++++++--------------------------------- symfony.lock | 3 - 3 files changed, 114 insertions(+), 225 deletions(-) diff --git a/composer.json b/composer.json index 3d25300f..14c19449 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,11 @@ "brick/math": "0.12.1 as 0.11.0", "composer/ca-bundle": "^1.3", "composer/package-versions-deprecated": "^1.11.99.5", - "doctrine/annotations": "1.14.3", "doctrine/data-fixtures": "^1.6.6", - "doctrine/dbal": "^3.4.6", + "doctrine/dbal": "^4.0.0", "doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-migrations-bundle": "^3.0", - "doctrine/orm": "^2.16", + "doctrine/orm": "^3.2.0", "dompdf/dompdf": "^v3.0.0", "erusev/parsedown": "^1.7", "florianv/swap": "^4.0", diff --git a/composer.lock b/composer.lock index 0db57d9b..d1140591 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "97361e6ad226561d1407dbafd8303465", + "content-hash": "a0319f019362e78fc8d643228989a5fd", "packages": [ { "name": "api-platform/core", @@ -534,30 +534,30 @@ }, { "name": "doctrine/annotations", - "version": "1.14.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", "shasum": "" }, "require": { - "doctrine/lexer": "^1 || ^2", + "doctrine/lexer": "^2 || ^3", "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/cache": "^5.4 || ^6", "vimeo/psalm": "^4.10" }, "suggest": { @@ -604,9 +604,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.3" + "source": "https://github.com/doctrine/annotations/tree/2.0.1" }, - "time": "2023-02-01T09:20:38+00:00" + "time": "2023-02-02T22:02:53+00:00" }, { "name": "doctrine/cache", @@ -787,97 +787,6 @@ ], "time": "2024-04-18T06:56:21+00:00" }, - { - "name": "doctrine/common", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0 || ^3.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "doctrine/collections": "^1", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^6.1", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.4.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2024-04-16T13:35:33+00:00" - }, { "name": "doctrine/data-fixtures", "version": "1.7.0", @@ -964,47 +873,42 @@ }, { "name": "doctrine/dbal", - "version": "3.8.4", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd" + "reference": "61d79c6e379a39dc1fea6b4e50a23dfc3cd2076a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b05e48a745f722801f55408d0dbd8003b403dbbd", - "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61d79c6e379a39dc1fea6b4e50a23dfc3cd2076a", + "reference": "61d79c6e379a39dc1fea6b4e50a23dfc3cd2076a", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "php": "^8.1", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", + "jetbrains/phpstorm-stubs": "2023.2", "phpstan/phpstan": "1.10.58", + "phpstan/phpstan-phpunit": "1.3.15", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.16", + "phpunit/phpunit": "10.5.9", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.9.0", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0", + "vimeo/psalm": "5.21.1" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -1057,7 +961,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.4" + "source": "https://github.com/doctrine/dbal/tree/4.0.2" }, "funding": [ { @@ -1073,7 +977,7 @@ "type": "tidelift" } ], - "time": "2024-04-25T07:04:44+00:00" + "time": "2024-04-25T08:29:52+00:00" }, { "name": "doctrine/deprecations", @@ -1588,28 +1492,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.21" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -1646,7 +1549,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1662,7 +1565,7 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:35:39+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "doctrine/migrations", @@ -1768,61 +1671,48 @@ }, { "name": "doctrine/orm", - "version": "2.19.5", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "94986af28452da42a46a4489d1c958a2e5d710e5" + "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/94986af28452da42a46a4489d1c958a2e5d710e5", - "reference": "94986af28452da42a46a4489d1c958a2e5d710e5", + "url": "https://api.github.com/repos/doctrine/orm/zipball/37946d3a21ddf837c0d84f8156ee60a92102e332", + "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5 || ^2.1", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^2 || ^3", - "doctrine/persistence": "^2.4 || ^3", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1", "ext-ctype": "*", - "php": "^7.1 || ^8.0", + "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { - "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.59", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.11.1", + "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "4.30.0 || 5.22.2" + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.24.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, - "bin": [ - "bin/doctrine" - ], "type": "library", "autoload": { "psr-4": { @@ -1863,9 +1753,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.19.5" + "source": "https://github.com/doctrine/orm/tree/3.2.0" }, - "time": "2024-04-30T06:49:54+00:00" + "time": "2024-05-23T14:27:52+00:00" }, { "name": "doctrine/persistence", @@ -2634,7 +2524,7 @@ }, { "name": "gregwar/captcha-bundle", - "version": "v2.2.1", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/Gregwar/CaptchaBundle.git", @@ -2695,7 +2585,7 @@ ], "support": { "issues": "https://github.com/Gregwar/CaptchaBundle/issues", - "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.2.1" + "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.3.0" }, "time": "2024-06-06T13:14:57+00:00" }, @@ -11976,16 +11866,16 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.17.0", + "version": "v2.18.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "b828a32fe9f75500d26b563cc01874657162c413" + "reference": "9323437da427e123d8f9b76b19fa9a60a76d45a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/b828a32fe9f75500d26b563cc01874657162c413", - "reference": "b828a32fe9f75500d26b563cc01874657162c413", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/9323437da427e123d8f9b76b19fa9a60a76d45a0", + "reference": "9323437da427e123d8f9b76b19fa9a60a76d45a0", "shasum": "" }, "require": { @@ -12025,7 +11915,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.17.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.18.0" }, "funding": [ { @@ -12041,7 +11931,7 @@ "type": "tidelift" } ], - "time": "2024-04-21T10:23:35+00:00" + "time": "2024-06-01T17:50:20+00:00" }, { "name": "symfony/stopwatch", @@ -12633,16 +12523,16 @@ }, { "name": "symfony/ux-translator", - "version": "v2.17.0", + "version": "v2.18.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "93ad2ca9725e4eb66d64f909c321cc16fafc7b15" + "reference": "bacc23ca4db239f4c6b24af6bb97136edc1f0b7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/93ad2ca9725e4eb66d64f909c321cc16fafc7b15", - "reference": "93ad2ca9725e4eb66d64f909c321cc16fafc7b15", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/bacc23ca4db239f4c6b24af6bb97136edc1f0b7d", + "reference": "bacc23ca4db239f4c6b24af6bb97136edc1f0b7d", "shasum": "" }, "require": { @@ -12689,7 +12579,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.17.0" + "source": "https://github.com/symfony/ux-translator/tree/v2.18.0" }, "funding": [ { @@ -12705,20 +12595,20 @@ "type": "tidelift" } ], - "time": "2024-04-19T06:36:45+00:00" + "time": "2024-06-01T17:50:16+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.17.0", + "version": "v2.18.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "7093e20d7ca599902a7d1bf4d831849fd78befdb" + "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/7093e20d7ca599902a7d1bf4d831849fd78befdb", - "reference": "7093e20d7ca599902a7d1bf4d831849fd78befdb", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/e447231ddcc09ab68d29047f47d31a524837dc7a", + "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a", "shasum": "" }, "require": { @@ -12729,6 +12619,7 @@ "symfony/flex": "<1.13" }, "require-dev": { + "dbrekelmans/bdi": "dev-main", "doctrine/doctrine-bundle": "^2.4.3", "doctrine/orm": "^2.8 | 3.0", "phpstan/phpstan": "^1.10", @@ -12785,7 +12676,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.17.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.18.0" }, "funding": [ { @@ -12801,7 +12692,7 @@ "type": "tidelift" } ], - "time": "2024-04-22T13:58:54+00:00" + "time": "2024-06-01T17:56:14+00:00" }, { "name": "symfony/validator", @@ -15816,16 +15707,16 @@ }, { "name": "phpstan/phpstan-doctrine", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "a223e357c5f153b446b8a5da57dbc1132eb7a88d" + "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/a223e357c5f153b446b8a5da57dbc1132eb7a88d", - "reference": "a223e357c5f153b446b8a5da57dbc1132eb7a88d", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", + "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", "shasum": "" }, "require": { @@ -15882,9 +15773,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.1" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.3" }, - "time": "2024-05-28T15:37:29+00:00" + "time": "2024-06-08T05:48:50+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -15937,16 +15828,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28" + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/af6ae0f4b91bc080265e80776af26da3e5befb28", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/bca27f1701fc1a297749e6c2a1e3da4462c1a6af", + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af", "shasum": "" }, "require": { @@ -16003,9 +15894,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.3" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.4" }, - "time": "2024-05-30T15:01:27+00:00" + "time": "2024-06-07T09:43:24+00:00" }, { "name": "phpunit/php-code-coverage", @@ -16431,23 +16322,23 @@ }, { "name": "psalm/plugin-symfony", - "version": "v5.04", + "version": "v5.2.1", "source": { "type": "git", "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "2a3f81e62278f488a21b70603df4cfb845af70ad" + "reference": "0fe09bf25cb64deef73e8eab7be89f9401d2f5b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/2a3f81e62278f488a21b70603df4cfb845af70ad", - "reference": "2a3f81e62278f488a21b70603df4cfb845af70ad", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/0fe09bf25cb64deef73e8eab7be89f9401d2f5b8", + "reference": "0fe09bf25cb64deef73e8eab7be89f9401d2f5b8", "shasum": "" }, "require": { "ext-simplexml": "*", - "php": "^7.4 || ^8.0", - "symfony/framework-bundle": "^5.0 || ^6.0", - "vimeo/psalm": "^5.1" + "php": "^8.1", + "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "^5.24" }, "require-dev": { "doctrine/annotations": "^1.8|^2", @@ -16455,10 +16346,10 @@ "phpunit/phpunit": "~7.5 || ~9.5", "symfony/cache-contracts": "^1.0 || ^2.0", "symfony/console": "*", - "symfony/form": "^5.0 || ^6.0", - "symfony/messenger": "^5.0 || ^6.0", - "symfony/security-guard": "*", - "symfony/serializer": "^5.0 || ^6.0", + "symfony/form": "^5.0 || ^6.0 || ^7.0", + "symfony/messenger": "^5.0 || ^6.0 || ^7.0", + "symfony/security-core": "*", + "symfony/serializer": "^5.0 || ^6.0 || ^7.0", "symfony/validator": "*", "twig/twig": "^2.10 || ^3.0", "weirdan/codeception-psalm-module": "dev-master" @@ -16490,9 +16381,9 @@ "description": "Psalm Plugin for Symfony", "support": { "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", - "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.04" + "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.2.1" }, - "time": "2023-11-10T07:14:46+00:00" + "time": "2024-06-05T21:20:57+00:00" }, { "name": "rector/rector", @@ -16556,12 +16447,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "01c19f1a89daf51e8355cc0b7e3113d5274929b5" + "reference": "cde5826457b1afd988a50206946cf6512b75ac7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/01c19f1a89daf51e8355cc0b7e3113d5274929b5", - "reference": "01c19f1a89daf51e8355cc0b7e3113d5274929b5", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/cde5826457b1afd988a50206946cf6512b75ac7c", + "reference": "cde5826457b1afd988a50206946cf6512b75ac7c", "shasum": "" }, "conflict": { @@ -16570,7 +16461,7 @@ "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.21|>=2022.04.1,<2022.10.12|>=2023.04.1,<2023.10.14|>=2024.04.1,<2024.04.4", - "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", + "aimeos/aimeos-core": "<2024.04.7", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", @@ -16621,6 +16512,7 @@ "bmarshall511/wordpress_zero_spam": "<5.2.13", "bolt/bolt": "<3.7.2", "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", "bottelet/flarepoint": "<2.2.1", "bref/bref": "<2.1.17", "brightlocal/phpwhois": "<=4.2.5", @@ -16772,7 +16664,7 @@ "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "getformwork/formwork": "<1.13", + "getformwork/formwork": "<1.13.1|==2.0.0.0-beta1", "getgrav/grav": "<1.7.46", "getkirby/cms": "<4.1.1", "getkirby/kirby": "<=2.5.12", @@ -17121,6 +17013,7 @@ "studio-42/elfinder": "<2.1.62", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", + "sulu/form-bundle": ">=2,<2.5.3", "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", @@ -17290,7 +17183,7 @@ "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", "zendframework/zend-validator": ">=2.3,<2.3.6", "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", @@ -17349,7 +17242,7 @@ "type": "tidelift" } ], - "time": "2024-06-05T14:05:01+00:00" + "time": "2024-06-07T22:04:16+00:00" }, { "name": "sebastian/cli-parser", diff --git a/symfony.lock b/symfony.lock index 1e04eedd..8f5b824f 100644 --- a/symfony.lock +++ b/symfony.lock @@ -74,9 +74,6 @@ "doctrine/collections": { "version": "v1.5.0" }, - "doctrine/common": { - "version": "v2.10.0" - }, "doctrine/data-fixtures": { "version": "v1.3.2" }, From 43ca54365180e2983396e660ef0df642f1fa96a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:15:14 +0200 Subject: [PATCH 06/56] Fixed wrong signature of UTCDateTimeType --- src/Doctrine/Types/UTCDateTimeType.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index c7140252..821a43d2 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -64,11 +64,11 @@ class UTCDateTimeType extends DateTimeType * * @param T $value * - * @return (T is null ? null : DateTimeInterface) - * + * @return DateTime|(T is null ? null : DateTimeInterface) + * * @template T */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeInterface + public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime { if (!self::$utc_timezone instanceof \DateTimeZone) { self::$utc_timezone = new DateTimeZone('UTC'); @@ -85,7 +85,7 @@ class UTCDateTimeType extends DateTimeType ); if (!$converted) { - throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString()); + throw new ConversionException("Failed to convert PHP value to a DateTime object."); } return $converted; From 777bfed8131e9f72a1f267af1d33f74e2d03b70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:28:46 +0200 Subject: [PATCH 07/56] Fixed doctrine middlewares We now look directly onto the driver arguments instead of retrieving a database platform, for which we would need the database version. As we modify driver specific options there, this might be the better choice anyway --- src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php | 2 +- .../Middleware/SQLiteRegexExtensionMiddlewareDriver.php | 2 +- src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php index 2f6d39dd..fd46370f 100644 --- a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php @@ -42,7 +42,7 @@ class MySQLSSLConnectionMiddlewareDriver extends AbstractDriverMiddleware public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter - if($this->enabled && $this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + if($this->enabled && $params['driver'] === 'pdo_mysql') { $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = CaBundle::getSystemCaRootBundlePath(); $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->verify; } diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index ee851052..38160ec0 100644 --- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -41,7 +41,7 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware $connection = parent::connect($params); // TODO: Change the autogenerated stub //Then add the functions if we are on SQLite - if ($this->getDatabasePlatform() instanceof SqlitePlatform) { + if ($params['driver'] === 'pdo_sqlite') { $native_connection = $connection->getNativeConnection(); //Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation diff --git a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php index e581b5c1..defda2f1 100644 --- a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php @@ -35,7 +35,7 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter - if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + if($params['driver'] === 'pdo_mysql') { //1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value $params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))'; } From 7d9be5ae76bfa46c814d700f49569da4387a8b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:36:00 +0200 Subject: [PATCH 08/56] Fixed wrong casing of SQLitePlatform --- src/Command/BackupCommand.php | 4 ++-- .../Middleware/MySQLSSLConnectionMiddlewareDriver.php | 1 - .../SQLiteRegexExtensionMiddlewareDriver.php | 1 - src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php | 1 - src/Migration/AbstractMultiPlatformMigration.php | 4 ++-- src/Services/Misc/DBInfoHelper.php | 10 +++++----- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Command/BackupCommand.php b/src/Command/BackupCommand.php index ef7d038f..e71713d0 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\ORM\EntityManagerInterface; use PhpZip\Constants\ZipCompressionMethod; @@ -157,7 +157,7 @@ class BackupCommand extends Command $io->error('Could not dump database: '.$e->getMessage()); $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!'); } - } elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) { + } elseif ($connection->getDatabasePlatform() instanceof SQLitePlatform) { $io->note('SQLite database detected. Copy DB file to ZIP...'); $params = $connection->getParams(); $zip->addFile($params['path'], 'var/app.db'); diff --git a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php index fd46370f..2a707e1f 100644 --- a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php @@ -27,7 +27,6 @@ use Composer\CaBundle\CaBundle; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; /** * This middleware sets SSL options for MySQL connections diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index 38160ec0..8421e818 100644 --- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -26,7 +26,6 @@ namespace App\Doctrine\Middleware; use App\Exceptions\InvalidRegexException; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\SqlitePlatform; /** * This middleware is used to add the regexp operator to the SQLite platform. diff --git a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php index defda2f1..d05b6b9c 100644 --- a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php @@ -24,7 +24,6 @@ namespace App\Doctrine\Middleware; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; /** * This command sets the initial command parameter for MySQL connections, so we can set the SQL mode diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index bdad175f..bc2b3f19 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -26,7 +26,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; @@ -166,7 +166,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Misc/DBInfoHelper.php index b093fda8..620da702 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Misc/DBInfoHelper.php @@ -26,7 +26,7 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\ORM\EntityManagerInterface; /** @@ -51,7 +51,7 @@ class DBInfoHelper return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } @@ -72,7 +72,7 @@ class DBInfoHelper return $this->connection->fetchOne('SELECT VERSION()'); } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return $this->connection->fetchOne('SELECT sqlite_version()'); } @@ -98,7 +98,7 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { try { return (int) $this->connection->fetchOne('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();'); } catch (Exception) { @@ -138,7 +138,7 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } From 60325e797da3db98c96705dcb8f4fcab40f6ae9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:37:18 +0200 Subject: [PATCH 09/56] Fixed the use of the removed ClassMetadataInfo constants --- src/Doctrine/Types/TinyIntType.php | 4 ++-- .../AttachmentContainingDBElementRepository.php | 4 ++-- .../PartKeeprImporter/PKImportHelperTrait.php | 4 ++-- src/Services/LogSystem/TimeTravel.php | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index 70a02e14..5f31f882 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -24,7 +24,7 @@ namespace App\Doctrine\Types; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\Type; /** @@ -36,7 +36,7 @@ class TinyIntType extends Type public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { //MySQL and SQLite know the TINYINT type directly - if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SqlitePlatform) { + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { return 'TINYINT'; } diff --git a/src/Repository/AttachmentContainingDBElementRepository.php b/src/Repository/AttachmentContainingDBElementRepository.php index 7f00f87f..7c81c38a 100644 --- a/src/Repository/AttachmentContainingDBElementRepository.php +++ b/src/Repository/AttachmentContainingDBElementRepository.php @@ -25,7 +25,7 @@ namespace App\Repository; use App\Doctrine\Helpers\FieldHelper; use App\Entity\Attachments\AttachmentContainingDBElement; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; /** * @template TEntityClass of AttachmentContainingDBElement @@ -70,7 +70,7 @@ class AttachmentContainingDBElementRepository extends NamedDBElementRepository $q = $qb->getQuery(); - $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadataInfo::FETCH_EAGER); + $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadata::FETCH_EAGER); $result = $q->getResult(); diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 681a1371..ea897b9f 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -30,7 +30,7 @@ use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Contracts\TimeStampableInterface; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** @@ -212,7 +212,7 @@ trait PKImportHelperTrait $id = (int) $id; $metadata = $this->em->getClassMetadata($element::class); - $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdentifierValues($element, ['id' => $id]); } diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 400e85f5..c6669678 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -35,7 +35,7 @@ use Brick\Math\BigDecimal; use DateTime; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Exception; use InvalidArgumentException; @@ -136,16 +136,16 @@ class TimeTravel //Revert many-to-one association (one element in property) if ( - ClassMetadataInfo::MANY_TO_ONE === $mapping['type'] - || ClassMetadataInfo::ONE_TO_ONE === $mapping['type'] + ClassMetadata::MANY_TO_ONE === $mapping['type'] + || ClassMetadata::ONE_TO_ONE === $mapping['type'] ) { $target_element = $this->getField($element, $field); if (null !== $target_element && $element->getLastModified() > $timestamp) { $this->revertEntityToTimestamp($target_element, $timestamp, $reverted_elements); } } elseif ( //Revert *_TO_MANY associations (collection properties) - (ClassMetadataInfo::MANY_TO_MANY === $mapping['type'] - || ClassMetadataInfo::ONE_TO_MANY === $mapping['type']) + (ClassMetadata::MANY_TO_MANY === $mapping['type'] + || ClassMetadata::ONE_TO_MANY === $mapping['type']) && !$mapping['isOwningSide'] ) { $target_elements = $this->getField($element, $field); From 78671b0bfea6642f2f3b030ac2b0df62e402ecd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:51:11 +0200 Subject: [PATCH 10/56] Fixed errors that query builder setParameters now expects an ArrayCollection instead of an array --- src/Repository/LogEntryRepository.php | 17 +++++++++-------- src/Repository/UserRepository.php | 5 +++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 82e4bdd8..18cbd4f8 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -30,6 +30,7 @@ use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\LogTargetType; use App\Entity\UserSystem\User; +use Doctrine\Common\Collections\ArrayCollection; use RuntimeException; /** @@ -85,10 +86,10 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC') ->setMaxResults(1); - $qb->setParameters([ + $qb->setParameters(new ArrayCollection([ 'target_type' => LogTargetType::fromElementClass($class), 'target_id' => $id, - ]); + ])); $query = $qb->getQuery(); @@ -121,11 +122,11 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ + $qb->setParameters(new ArrayCollection([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $until, - ]); + ])); $query = $qb->getQuery(); @@ -147,11 +148,11 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ + $qb->setParameters(new ArrayCollection([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $timestamp, - ]); + ])); $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); @@ -232,10 +233,10 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ + $qb->setParameters(new ArrayCollection([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), - ]); + ])); $query = $qb->getQuery(); $query->setMaxResults(1); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index fa95e83d..1dde751c 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\UserSystem\User; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; @@ -97,10 +98,10 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU ->where('u.name = (:name)') ->orWhere('u.email = (:email)'); - $qb->setParameters([ + $qb->setParameters(new ArrayCollection([ 'email' => $name_or_password, 'name' => $name_or_password, - ]); + ])); try { return $qb->getQuery()->getOneOrNullResult(); From 0d445b6a218270097bf7d679a8cb54d5da7e1091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:51:41 +0200 Subject: [PATCH 11/56] Fixed some minor inspection issues caused by the doctrine upgrades --- src/DataTables/Adapters/CustomFetchJoinORMAdapter.php | 2 +- src/Doctrine/Types/UTCDateTimeType.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php index b296c4fa..ff69a69e 100644 --- a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php +++ b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php @@ -50,6 +50,6 @@ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter $paginator = new Paginator($qb_without_group_by); - return $paginator->count() ?? 0; + return $paginator->count(); } } diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index 821a43d2..11ece822 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -64,8 +64,6 @@ class UTCDateTimeType extends DateTimeType * * @param T $value * - * @return DateTime|(T is null ? null : DateTimeInterface) - * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime From bd640c19a46470fc4eef383e417e200d52a1b422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:55:12 +0200 Subject: [PATCH 12/56] Fixed type of token object is now a property instead of array value in Field2 --- src/Doctrine/Functions/Field2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Doctrine/Functions/Field2.php b/src/Doctrine/Functions/Field2.php index 0e65062f..3b57fa27 100644 --- a/src/Doctrine/Functions/Field2.php +++ b/src/Doctrine/Functions/Field2.php @@ -50,7 +50,7 @@ class Field2 extends FunctionNode $lexer = $parser->getLexer(); while (count($this->values) < 1 || - $lexer->lookahead['type'] != TokenType::T_CLOSE_PARENTHESIS) { + $lexer->lookahead->type !== TokenType::T_CLOSE_PARENTHESIS) { $parser->match(TokenType::T_COMMA); $this->values[] = $parser->ArithmeticPrimary(); } From afb816cc41048a9f6a5f08f9f2ece5f3b82db5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 9 Jun 2024 23:58:03 +0200 Subject: [PATCH 13/56] Use the getReference function in TimeTravel service instead of the removed getPartialReference() This is probably the better choice anyway --- src/Services/LogSystem/TimeTravel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index c6669678..1ec742e3 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -219,7 +219,7 @@ class TimeTravel $target_class = $mapping['targetEntity']; //Try to extract the old ID: if (is_array($data) && isset($data['@id'])) { - $entity = $this->em->getPartialReference($target_class, $data['@id']); + $entity = $this->em->getReference($target_class, $data['@id']); $this->setField($element, $field, $entity); } } From 5eb29746af62d36a90601968f13ed73d6c2e6f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 00:04:22 +0200 Subject: [PATCH 14/56] Revert "Fixed errors that query builder setParameters now expects an ArrayCollection instead of an array" This reverts commit 78671b0bfea6642f2f3b030ac2b0df62e402ecd2. --- src/Repository/LogEntryRepository.php | 17 ++++++++--------- src/Repository/UserRepository.php | 5 ++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 18cbd4f8..82e4bdd8 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -30,7 +30,6 @@ use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\LogTargetType; use App\Entity\UserSystem\User; -use Doctrine\Common\Collections\ArrayCollection; use RuntimeException; /** @@ -86,10 +85,10 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC') ->setMaxResults(1); - $qb->setParameters(new ArrayCollection([ + $qb->setParameters([ 'target_type' => LogTargetType::fromElementClass($class), 'target_id' => $id, - ])); + ]); $query = $qb->getQuery(); @@ -122,11 +121,11 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters(new ArrayCollection([ + $qb->setParameters([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $until, - ])); + ]); $query = $qb->getQuery(); @@ -148,11 +147,11 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters(new ArrayCollection([ + $qb->setParameters([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $timestamp, - ])); + ]); $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); @@ -233,10 +232,10 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters(new ArrayCollection([ + $qb->setParameters([ 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), - ])); + ]); $query = $qb->getQuery(); $query->setMaxResults(1); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 1dde751c..fa95e83d 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\UserSystem\User; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; @@ -98,10 +97,10 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU ->where('u.name = (:name)') ->orWhere('u.email = (:email)'); - $qb->setParameters(new ArrayCollection([ + $qb->setParameters([ 'email' => $name_or_password, 'name' => $name_or_password, - ])); + ]); try { return $qb->getQuery()->getOneOrNullResult(); From 1830e9da3daa9a200dcf4e4926b413acca6b9e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 00:08:53 +0200 Subject: [PATCH 15/56] Fixed errors caused by change on how setParameters on query builder works We replaced them by individual setParameter calls as this seems to be the easiest way to fix this --- src/Repository/LogEntryRepository.php | 29 ++++++++++----------------- src/Repository/UserRepository.php | 8 ++++---- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 82e4bdd8..8e16cdf8 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -85,10 +85,8 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC') ->setMaxResults(1); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($class), - 'target_id' => $id, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($class)); + $qb->setParameter('target_id', $id); $query = $qb->getQuery(); @@ -121,11 +119,10 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $until, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $until); + $query = $qb->getQuery(); @@ -147,11 +144,9 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.timestamp >= :until') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $timestamp, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $timestamp); $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); @@ -232,10 +227,8 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); $query = $qb->getQuery(); $query->setMaxResults(1); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index fa95e83d..9f24520d 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\UserSystem\User; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\Query\Parameter; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -97,10 +99,8 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU ->where('u.name = (:name)') ->orWhere('u.email = (:email)'); - $qb->setParameters([ - 'email' => $name_or_password, - 'name' => $name_or_password, - ]); + $qb->setParameter('email', $name_or_password); + $qb->setParameter('name', $name_or_password); try { return $qb->getQuery()->getOneOrNullResult(); From 4d927c5870e8e7bececad5585936c73d942f8480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 20:17:57 +0200 Subject: [PATCH 16/56] Use a better exception format for UTCDateTimeType --- src/Doctrine/Types/UTCDateTimeType.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index 11ece822..a6fda747 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -28,6 +28,7 @@ use DateTimeZone; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; /** * This DateTimeType all dates to UTC, so it can be later used with the timezones. @@ -83,7 +84,11 @@ class UTCDateTimeType extends DateTimeType ); if (!$converted) { - throw new ConversionException("Failed to convert PHP value to a DateTime object."); + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); } return $converted; From 777f6ba738d24689695cd5bf1380e10ec1139bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 20:47:06 +0200 Subject: [PATCH 17/56] Fixed error caused by immutable datetime passed to mutable datetime doctrine type --- src/DataFixtures/APITokenFixtures.php | 2 +- src/Entity/OAuthToken.php | 8 ++++---- src/Entity/UserSystem/ApiToken.php | 10 +++++----- src/Security/ApiTokenAuthenticator.php | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DataFixtures/APITokenFixtures.php b/src/DataFixtures/APITokenFixtures.php index 4bcf3a60..fbb4bdd2 100644 --- a/src/DataFixtures/APITokenFixtures.php +++ b/src/DataFixtures/APITokenFixtures.php @@ -75,7 +75,7 @@ class APITokenFixtures extends Fixture implements DependentFixtureInterface $expired_token->setUser($admin_user); $expired_token->setLevel(ApiTokenLevel::FULL); $expired_token->setName('expired'); - $expired_token->setValidUntil(new \DateTimeImmutable('-1 day')); + $expired_token->setValidUntil(new \DateTime('-1 day')); $this->setTokenSecret($expired_token, self::TOKEN_EXPIRED); $manager->persist($expired_token); diff --git a/src/Entity/OAuthToken.php b/src/Entity/OAuthToken.php index 84b3279c..2ecf9342 100644 --- a/src/Entity/OAuthToken.php +++ b/src/Entity/OAuthToken.php @@ -41,9 +41,9 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface #[ORM\Column(type: 'text', nullable: true)] private ?string $token = null; - /** @var \DateTimeInterface The date when the token expires */ + /** @var \DateTimeImmutable|null The date when the token expires */ #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - private ?\DateTimeInterface $expires_at = null; + private ?\DateTimeImmutable $expires_at = null; /** @var string|null The refresh token for the OAuth2 auth */ #[ORM\Column(type: 'text', nullable: true)] @@ -54,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface */ private const DEFAULT_EXPIRATION_TIME = 3600; - public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeInterface $expires_at = null) + public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeImmutable $expires_at = null) { //If token is given, you also have to give the expires_at date if ($token !== null && $expires_at === null) { @@ -82,7 +82,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface ); } - private static function unixTimestampToDatetime(int $timestamp): \DateTimeInterface + private static function unixTimestampToDatetime(int $timestamp): \DateTimeImmutable { return \DateTimeImmutable::createFromFormat('U', (string)$timestamp); } diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index cd73de90..751c08c1 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -78,7 +78,7 @@ class ApiToken implements TimeStampableInterface #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Groups('token:read')] #[Year2038BugWorkaround] - private ?\DateTimeInterface $valid_until; + private ?\DateTime $valid_until; #[ORM\Column(length: 68, unique: true)] private string $token; @@ -89,7 +89,7 @@ class ApiToken implements TimeStampableInterface #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Groups('token:read')] - private ?\DateTimeInterface $last_time_used = null; + private ?\DateTime $last_time_used = null; public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) { @@ -130,7 +130,7 @@ class ApiToken implements TimeStampableInterface return $this->valid_until === null || $this->valid_until > new \DateTime(); } - public function setValidUntil(?\DateTimeInterface $valid_until): ApiToken + public function setValidUntil(?\DateTime $valid_until): ApiToken { $this->valid_until = $valid_until; return $this; @@ -168,10 +168,10 @@ class ApiToken implements TimeStampableInterface /** * Sets the last time the token was used to authenticate. - * @param \DateTimeInterface|null $last_time_used + * @param \DateTime|null $last_time_used * @return ApiToken */ - public function setLastTimeUsed(?\DateTimeInterface $last_time_used): ApiToken + public function setLastTimeUsed(?\DateTime $last_time_used): ApiToken { $this->last_time_used = $last_time_used; return $this; diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php index 23ab68b9..5d121556 100644 --- a/src/Security/ApiTokenAuthenticator.php +++ b/src/Security/ApiTokenAuthenticator.php @@ -75,7 +75,7 @@ class ApiTokenAuthenticator implements AuthenticatorInterface $old_time = $token->getLastTimeUsed(); //Set the last used date of the token - $token->setLastTimeUsed(new \DateTimeImmutable()); + $token->setLastTimeUsed(new \DateTime()); //Only flush the token if the last used date change is more than 10 minutes //For performance reasons we don't want to flush the token every time it is used, but only if it is used more than 10 minutes after the last time it was used //If a flush is later in the code we don't want to flush the token again From 971bb92a8c4ef46cc518c100d3a9c0ecf880521d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:11:11 +0200 Subject: [PATCH 18/56] Fixed error caused by ArrayType fields which is required by the webauthn bundle but was removed in doctrine/orm 4.0 We simple forward port the ArrayType class from orm 3.8 to fix this error --- config/packages/doctrine.yaml | 4 ++ src/Doctrine/Types/ArrayType.php | 116 +++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/Doctrine/Types/ArrayType.php diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 3a160030..8dac4552 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -17,6 +17,10 @@ doctrine: class: App\Doctrine\Types\BigDecimalType tinyint: class: App\Doctrine\Types\TinyIntType + + # This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity + array: + class: App\Doctrine\Types\ArrayType schema_filter: ~^(?!internal)~ # Only enable this when needed diff --git a/src/Doctrine/Types/ArrayType.php b/src/Doctrine/Types/ArrayType.php new file mode 100644 index 00000000..daab9b75 --- /dev/null +++ b/src/Doctrine/Types/ArrayType.php @@ -0,0 +1,116 @@ +. + */ + +declare(strict_types=1); + +namespace App\Doctrine\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Exception\SerializationFailed; +use Doctrine\DBAL\Types\Type; +use Doctrine\Deprecations\Deprecation; + +use function is_resource; +use function restore_error_handler; +use function serialize; +use function set_error_handler; +use function stream_get_contents; +use function unserialize; + +use const E_DEPRECATED; +use const E_USER_DEPRECATED; + +/** + * This class is taken from doctrine ORM 3.8. https://github.com/doctrine/dbal/blob/3.8.x/src/Types/ArrayType.php + * + * It was removed in doctrine ORM 4.0. However, we require it for backward compatibility with WebauthnKey. + * Therefore, we manually added it here as a custom type as a forward compatibility layer. + */ +class ArrayType extends Type +{ + /** + * {@inheritDoc} + */ + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + return $platform->getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string + { + return serialize($value); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed + { + if ($value === null) { + return null; + } + + $value = is_resource($value) ? stream_get_contents($value) : $value; + + set_error_handler(function (int $code, string $message): bool { + if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { + return false; + } + + //Change to original code. Use SerializationFailed instead of ConversionException. + throw new SerializationFailed("Serialization failed (Code $code): " . $message); + }); + + try { + //Change to original code. Use false for allowed_classes, to avoid unsafe unserialization of objects. + return unserialize($value, ['allowed_classes' => false]); + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritDoc} + */ + public function getName(): string + { + return "array"; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} \ No newline at end of file From fe732ecf456e021d7d0b1122bdb8792d71975f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:16:46 +0200 Subject: [PATCH 19/56] Fixed minor inspection issue --- src/Doctrine/Purger/ResetAutoIncrementORMPurger.php | 2 ++ .../Validator/Constraints/NoneOfItsChildrenValidatorTest.php | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php index e596a8b6..20adc751 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php +++ b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php @@ -207,6 +207,8 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface return 'ALTER TABLE '.$tableIdentifier->getQuotedName($platform).' AUTO_INCREMENT = 1;'; } + throw new \RuntimeException("Resetting autoincrement is not supported on this platform!"); + //This seems to cause problems somehow /*if ($platform instanceof SqlitePlatform) { return 'DELETE FROM `sqlite_sequence` WHERE name = \''.$tableIdentifier->getQuotedName($platform).'\';'; diff --git a/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php b/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php index 39eb9336..973e6cfa 100644 --- a/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php +++ b/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php @@ -57,10 +57,6 @@ class NoneOfItsChildrenValidatorTest extends ConstraintValidatorTestCase $this->child1_2->setName('child1_2')->setParent($this->child1); } - private function getDummyObjects(): array - { - $obj1 = new AttachmentType(); - } protected function createValidator(): NoneOfItsChildrenValidator { From 07c7f07c35244a269f0f1846e5914df1631fd865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:25:23 +0200 Subject: [PATCH 20/56] Removed usage of partial flush which is not supported with the recent doctrine ORM version --- src/Services/OAuth/OAuthTokenManager.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Services/OAuth/OAuthTokenManager.php b/src/Services/OAuth/OAuthTokenManager.php index f67e9c6b..dddde20a 100644 --- a/src/Services/OAuth/OAuthTokenManager.php +++ b/src/Services/OAuth/OAuthTokenManager.php @@ -50,8 +50,7 @@ final class OAuthTokenManager if ($tokenEntity) { $tokenEntity->replaceWithNewToken($token); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + $this->entityManager->flush(); //We are done return $tokenEntity; @@ -60,8 +59,8 @@ final class OAuthTokenManager //If the token was not existing, we create a new one $tokenEntity = OAuthToken::fromAccessToken($token, $app_name); $this->entityManager->persist($tokenEntity); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + + $this->entityManager->flush(); return $tokenEntity; } @@ -113,9 +112,7 @@ final class OAuthTokenManager //Persist the token $token->replaceWithNewToken($new_token); - - //@phpstan-ignore-next-line - $this->entityManager->flush($token); + $this->entityManager->flush(); return $token; } From d40ce470d3e7f738b050a43fe2acdb0930594dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:25:51 +0200 Subject: [PATCH 21/56] Fixed some invalid class constant use in ResetAutoIncrementORMPurger --- src/Doctrine/Purger/ResetAutoIncrementORMPurger.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php index 20adc751..0fbf6cdb 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php +++ b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php @@ -31,8 +31,6 @@ use Doctrine\DBAL\Schema\Identifier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; - use function array_reverse; use function assert; use function count; @@ -280,7 +278,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface foreach ($classes as $class) { foreach ($class->associationMappings as $assoc) { - if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadataInfo::MANY_TO_MANY) { + if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) { continue; } From 427b8659c9fb16a4adbcbb552146f6f03bf49e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:43:25 +0200 Subject: [PATCH 22/56] Added MySQL migrations required by the doctrine upgrades The new ORM versions, do not have anymore column comments, so these got removed --- migrations/Version20240606203053.php | 34 +++++++++++++++++++++++++--- src/Doctrine/Types/TinyIntType.php | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index 326f7aa2..e6d75a53 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -19,7 +19,7 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function getDescription(): string { - return 'Initial schema for Postgres'; + return 'Initial schema for Postgres and apply changes to MySQL and SQLite caused by the doctrine upgrade'; } public function postgreSQLUp(Schema $schema): void @@ -378,12 +378,40 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function mySQLUp(Schema $schema): void { - $this->warnIf(true, "Migration not needed for MySQL"); + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL'); + //Set empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `groups` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data JSON NOT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `log` SET extra = "{}" WHERE extra = ""'); + $this->addSql('ALTER TABLE `log` CHANGE level level TINYINT NOT NULL, CHANGE extra extra JSON NOT NULL'); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL'); + $this->addSql('ALTER TABLE pricedetails CHANGE price price NUMERIC(11, 5) NOT NULL'); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL'); + $this->addSql('ALTER TABLE suppliers CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `users` SET settings = "{}" WHERE settings = ""'); + $this->addSql('UPDATE `users` SET backup_codes = "{}" WHERE backup_codes = ""'); + $this->addSql('UPDATE `users` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `users` CHANGE settings settings JSON NOT NULL, CHANGE backup_codes backup_codes JSON NOT NULL, CHANGE permissions_data permissions_data JSON NOT NULL'); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL'); + } public function mySQLDown(Schema $schema): void { - $this->warnIf(true, "Migration not needed for MySQL"); + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL COMMENT \'(DC2Type:tinyint)\', CHANGE extra extra LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE `pricedetails` CHANGE price price NUMERIC(11, 5) NOT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `suppliers` CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `users` CHANGE backup_codes backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE settings settings LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE transports transports LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', CHANGE trust_path trust_path LONGTEXT NOT NULL COMMENT \'(DC2Type:trust_path)\', CHANGE aaguid aaguid TINYTEXT NOT NULL COMMENT \'(DC2Type:aaguid)\', CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE other_ui other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\', CHANGE last_time_used last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + } public function sqLiteUp(Schema $schema): void diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index 5f31f882..43a88655 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -37,7 +37,7 @@ class TinyIntType extends Type { //MySQL and SQLite know the TINYINT type directly if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { - return 'TINYINT'; + return 'TINYINT(1)'; } //For other platforms, we use the smallest integer type available From 8ee3aaf4f4b4d6a35b96b7ea67acb54af3666d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 21:54:15 +0200 Subject: [PATCH 23/56] Added Sqlite migrations required by new doctrine/orm version and fixed migration duplication for sqlite --- migrations/Version20240606203053.php | 172 ++++++++++++++++++++++++++- src/Doctrine/Types/TinyIntType.php | 6 +- 2 files changed, 174 insertions(+), 4 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index e6d75a53..33093f2f 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -416,11 +416,179 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function sqLiteUp(Schema $schema): void { - $this->warnIf(true, "Migration not needed for Sqlite"); + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM groups'); + $this->addSql('DROP TABLE groups'); + $this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)'); + $this->addSql('CREATE INDEX group_idx_name ON groups (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level SMALLINT NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL, refresh_token CLOB DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, token, expires_at, refresh_token, name, last_modified, datetime_added) SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails'); + $this->addSql('DROP TABLE pricedetails'); + $this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM suppliers'); + $this->addSql('DROP TABLE suppliers'); + $this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)'); + $this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users'); + $this->addSql('DROP TABLE users'); + $this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)'); + $this->addSql('CREATE INDEX user_idx_username ON users (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL, aaguid CLOB NOT NULL, credential_public_key CLOB NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, last_time_used DATETIME DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } public function sqLiteDown(Schema $schema): void { - $this->warnIf(true, "Migration not needed for Sqlite"); + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , iso_code VARCHAR(255) NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM "groups"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "groups" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL --(DC2Type:tinyint) + , target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json) + , id_user INTEGER DEFAULT NULL, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, username, datetime, level, target_id, target_type, extra, id_user, type) SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , refresh_token CLOB DEFAULT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, name, last_modified, datetime_added, token, expires_at, refresh_token) SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM "pricedetails"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal) + , price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "pricedetails" (id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id) SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id) SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM "suppliers"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "suppliers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM "users"'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json) + , google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings CLOB NOT NULL --(DC2Type:json) + , backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , group_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "users" (id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id) SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id CLOB NOT NULL --(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path) + , aaguid CLOB NOT NULL --(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL --(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array) + , backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_time_used DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INTEGER DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id) SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } } diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index 43a88655..c2daeeca 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -35,8 +35,10 @@ class TinyIntType extends Type public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - //MySQL and SQLite know the TINYINT type directly - if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { + //MySQL knows the TINYINT type directly + //We do not use the TINYINT for sqlite, as it will be resolved to a BOOL type and bring problems with migrations + if ($platform instanceof AbstractMySQLPlatform ) { + //Use TINYINT(1) to allow for proper migration diffs return 'TINYINT(1)'; } From eab1c7096c4d571ac8febd8fc3277bba66fcc534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 22:38:46 +0200 Subject: [PATCH 24/56] Use the postgres migration code generated by the new doctrine orm versions This now uses IDENTITY columns for ID columns, which makes their behavior more similar to the behavior of MySQL and SQLite --- migrations/Version20240606203053.php | 122 +++++++-------------------- 1 file changed, 32 insertions(+), 90 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index 33093f2f..bf382bbb 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -24,110 +24,81 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function postgreSQLUp(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SEQUENCE api_tokens_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "attachment_types_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "attachments_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "categories_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE currencies_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "footprints_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "groups_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE label_profiles_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE log_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "manufacturers_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "measurement_units_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE oauth_tokens_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "orderdetails_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE parameters_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE part_association_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE part_lots_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "parts_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "pricedetails_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE project_bom_entries_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE projects_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "storelocations_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "suppliers_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE u2f_keys_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE "users_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE webauthn_keys_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE api_tokens (id INT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); - $this->addSql('CREATE TABLE "attachment_types" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, filetype_filter TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "attachment_types" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, filetype_filter TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)'); $this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)'); $this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)'); $this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)'); - $this->addSql('CREATE TABLE "attachments" (id INT NOT NULL, type_id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, path VARCHAR(255) NOT NULL, show_in_table BOOLEAN NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "attachments" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, path VARCHAR(255) NOT NULL, show_in_table BOOLEAN NOT NULL, type_id INT NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)'); $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)'); $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)'); $this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)'); $this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)'); $this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)'); - $this->addSql('CREATE TABLE "categories" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, partname_hint TEXT NOT NULL, partname_regex TEXT NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description TEXT NOT NULL, default_comment TEXT NOT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "categories" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, partname_hint TEXT NOT NULL, partname_regex TEXT NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description TEXT NOT NULL, default_comment TEXT NOT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)'); $this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)'); $this->addSql('CREATE INDEX category_idx_name ON "categories" (name)'); $this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)'); - $this->addSql('CREATE TABLE currencies (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE currencies (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); - $this->addSql('COMMENT ON COLUMN currencies.exchange_rate IS \'(DC2Type:big_decimal)\''); - $this->addSql('CREATE TABLE "footprints" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_footprint_3d INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "footprints" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_footprint_3d INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)'); $this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)'); $this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)'); $this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)'); $this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)'); - $this->addSql('CREATE TABLE "groups" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "groups" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data JSON NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); - $this->addSql('CREATE TABLE label_profiles (id INT NOT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, show_in_dropdown BOOLEAN NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css TEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE label_profiles (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, show_in_dropdown BOOLEAN NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css TEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines TEXT NOT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)'); - $this->addSql('CREATE TABLE log (id INT NOT NULL, id_user INT DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, level SMALLINT NOT NULL, target_id INT NOT NULL, target_type SMALLINT NOT NULL, extra JSON NOT NULL, type SMALLINT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE log (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(255) NOT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, level SMALLINT NOT NULL, target_id INT NOT NULL, target_type SMALLINT NOT NULL, extra JSON NOT NULL, id_user INT DEFAULT NULL, type SMALLINT NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); $this->addSql('CREATE INDEX log_idx_type ON log (type)'); $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); - $this->addSql('COMMENT ON COLUMN log.level IS \'(DC2Type:tinyint)\''); - $this->addSql('CREATE TABLE "manufacturers" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "manufacturers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)'); $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)'); $this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)'); $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)'); - $this->addSql('CREATE TABLE "measurement_units" (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "measurement_units" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)'); $this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)'); $this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)'); $this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)'); - $this->addSql('CREATE TABLE oauth_tokens (id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, token TEXT DEFAULT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refresh_token TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE oauth_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, token TEXT DEFAULT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refresh_token TEXT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); - $this->addSql('COMMENT ON COLUMN oauth_tokens.expires_at IS \'(DC2Type:datetime_immutable)\''); - $this->addSql('CREATE TABLE "orderdetails" (id INT NOT NULL, part_id INT NOT NULL, id_supplier INT DEFAULT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url TEXT NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "orderdetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url TEXT NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, part_id INT NOT NULL, id_supplier INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)'); $this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)'); $this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)'); - $this->addSql('CREATE TABLE parameters (id INT NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, symbol VARCHAR(255) NOT NULL, value_min DOUBLE PRECISION DEFAULT NULL, value_typical DOUBLE PRECISION DEFAULT NULL, value_max DOUBLE PRECISION DEFAULT NULL, unit VARCHAR(255) NOT NULL, value_text VARCHAR(255) NOT NULL, param_group VARCHAR(255) NOT NULL, type SMALLINT NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE parameters (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, symbol VARCHAR(255) NOT NULL, value_min DOUBLE PRECISION DEFAULT NULL, value_typical DOUBLE PRECISION DEFAULT NULL, value_max DOUBLE PRECISION DEFAULT NULL, unit VARCHAR(255) NOT NULL, value_text VARCHAR(255) NOT NULL, param_group VARCHAR(255) NOT NULL, type SMALLINT NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_69348FE1F1F2A24 ON parameters (element_id)'); $this->addSql('CREATE INDEX parameter_name_idx ON parameters (name)'); $this->addSql('CREATE INDEX parameter_group_idx ON parameters (param_group)'); $this->addSql('CREATE INDEX parameter_type_element_idx ON parameters (type, element_id)'); - $this->addSql('CREATE TABLE part_association (id INT NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE part_association (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)'); $this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)'); - $this->addSql('CREATE TABLE part_lots (id INT NOT NULL, id_store_location INT DEFAULT NULL, id_part INT NOT NULL, id_owner INT DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE part_lots (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_store_location INT DEFAULT NULL, id_part INT NOT NULL, id_owner INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); - $this->addSql('CREATE TABLE "parts" (id INT NOT NULL, id_preview_attachment INT DEFAULT NULL, id_category INT NOT NULL, id_footprint INT DEFAULT NULL, id_part_unit INT DEFAULT NULL, id_manufacturer INT DEFAULT NULL, order_orderdetails_id INT DEFAULT NULL, built_project_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags TEXT NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url TEXT NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INT NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "parts" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags TEXT NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url TEXT NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INT NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_category INT NOT NULL, id_footprint INT DEFAULT NULL, id_part_unit INT DEFAULT NULL, id_manufacturer INT DEFAULT NULL, order_orderdetails_id INT DEFAULT NULL, built_project_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); @@ -139,52 +110,42 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); - $this->addSql('CREATE TABLE "pricedetails" (id INT NOT NULL, id_currency INT DEFAULT NULL, orderdetails_id INT NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "pricedetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INT DEFAULT NULL, orderdetails_id INT NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); - $this->addSql('COMMENT ON COLUMN "pricedetails".price IS \'(DC2Type:big_decimal)\''); - $this->addSql('CREATE TABLE project_bom_entries (id INT NOT NULL, id_device INT DEFAULT NULL, id_part INT DEFAULT NULL, price_currency_id INT DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames TEXT NOT NULL, name VARCHAR(255) DEFAULT NULL, comment TEXT NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE project_bom_entries (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames TEXT NOT NULL, name VARCHAR(255) DEFAULT NULL, comment TEXT NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INT DEFAULT NULL, id_part INT DEFAULT NULL, price_currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); - $this->addSql('COMMENT ON COLUMN project_bom_entries.price IS \'(DC2Type:big_decimal)\''); - $this->addSql('CREATE TABLE projects (id INT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, order_quantity INT NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE projects (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, order_quantity INT NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)'); $this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)'); - $this->addSql('CREATE TABLE "storelocations" (id INT NOT NULL, parent_id INT DEFAULT NULL, storage_type_id INT DEFAULT NULL, id_owner INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "storelocations" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT false NOT NULL, parent_id INT DEFAULT NULL, storage_type_id INT DEFAULT NULL, id_owner INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)'); $this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)'); $this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)'); $this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)'); $this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)'); $this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)'); - $this->addSql('CREATE TABLE "suppliers" (id INT NOT NULL, parent_id INT DEFAULT NULL, default_currency_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "suppliers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, parent_id INT DEFAULT NULL, default_currency_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); - $this->addSql('COMMENT ON COLUMN "suppliers".shipping_costs IS \'(DC2Type:big_decimal)\''); - $this->addSql('CREATE TABLE u2f_keys (id INT NOT NULL, user_id INT DEFAULT NULL, key_handle VARCHAR(128) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate TEXT NOT NULL, counter VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE u2f_keys (key_handle VARCHAR(128) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate TEXT NOT NULL, counter VARCHAR(255) NOT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_4F4ADB4BA76ED395 ON u2f_keys (user_id)'); $this->addSql('CREATE UNIQUE INDEX user_unique ON u2f_keys (user_id, key_handle)'); - $this->addSql('CREATE TABLE "users" (id INT NOT NULL, group_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, currency_id INT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a TEXT NOT NULL, config_instock_comment_w TEXT NOT NULL, about_me TEXT NOT NULL, trusted_device_cookie_version INT NOT NULL, backup_codes JSON NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT false NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings JSON NOT NULL, backup_codes_generation_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, pw_reset_expires TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE "users" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a TEXT NOT NULL, config_instock_comment_w TEXT NOT NULL, about_me TEXT NOT NULL, trusted_device_cookie_version INT NOT NULL, backup_codes JSON NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT false NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings JSON NOT NULL, backup_codes_generation_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, pw_reset_expires TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data JSON NOT NULL, group_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); - $this->addSql('CREATE TABLE webauthn_keys (id INT NOT NULL, user_id INT DEFAULT NULL, public_key_credential_id TEXT NOT NULL, type VARCHAR(255) NOT NULL, transports TEXT NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path JSON NOT NULL, aaguid TEXT NOT NULL, credential_public_key TEXT NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INT NOT NULL, other_ui TEXT DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, name VARCHAR(255) NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id TEXT NOT NULL, type VARCHAR(255) NOT NULL, transports TEXT NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path JSON NOT NULL, aaguid TEXT NOT NULL, credential_public_key TEXT NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INT NOT NULL, other_ui TEXT DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); - $this->addSql('COMMENT ON COLUMN webauthn_keys.public_key_credential_id IS \'(DC2Type:base64)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.transports IS \'(DC2Type:array)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.trust_path IS \'(DC2Type:trust_path)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.aaguid IS \'(DC2Type:aaguid)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.credential_public_key IS \'(DC2Type:base64)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.other_ui IS \'(DC2Type:array)\''); - $this->addSql('COMMENT ON COLUMN webauthn_keys.last_time_used IS \'(DC2Type:datetime_immutable)\''); $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); @@ -254,6 +215,10 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme EOD; $this->addSql($sql); + //Increase the sequence for the groups, to avoid conflicts later + $this->addSql('SELECT setval(\'groups_id_seq\', 4)'); + + $admin_pw = $this->getInitalAdminPW(); $sql = <<addSql($sql); + + //Increase the sequence for the users, to avoid conflicts later + $this->addSql('SELECT setval(\'users_id_seq\', 3)'); } public function postgreSQLDown(Schema $schema): void { - // this down() migration is auto-generated, please modify it to your needs $this->addSql('CREATE SCHEMA public'); - $this->addSql('DROP SEQUENCE api_tokens_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "attachment_types_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "attachments_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "categories_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE currencies_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "footprints_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "groups_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE label_profiles_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE log_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "manufacturers_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "measurement_units_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE oauth_tokens_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "orderdetails_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE parameters_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE part_association_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE part_lots_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "parts_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "pricedetails_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE project_bom_entries_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE projects_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "storelocations_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE "suppliers_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE u2f_keys_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE "users_id_seq" CASCADE'); - $this->addSql('DROP SEQUENCE webauthn_keys_id_seq CASCADE'); $this->addSql('ALTER TABLE api_tokens DROP CONSTRAINT FK_2CAD560EA76ED395'); $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719727ACA70'); $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719EA7100A1'); From 0b53542716c0323d6269b5c4d3b2199011f7ffb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 22:52:11 +0200 Subject: [PATCH 25/56] Added postgres to github tests actions --- .github/workflows/tests.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f4610ae..e99434ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: php-versions: [ '8.1', '8.2', '8.3' ] - db-type: [ 'mysql', 'sqlite' ] + db-type: [ 'mysql', 'sqlite', 'postgres' ] env: # Note that we set DATABASE URL later based on our db-type matrix value @@ -30,13 +30,17 @@ jobs: steps: - name: Set Database env for MySQL - run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/test" >> $GITHUB_ENV + run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb" >> $GITHUB_ENV if: matrix.db-type == 'mysql' - name: Set Database env for SQLite run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV if: matrix.db-type == 'sqlite' + - name: Set Database env for PostgreSQL + run: echo "DATABASE_URL=postgresql://runner:@127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV + if: matrix.db-type == 'postgres' + - name: Checkout uses: actions/checkout@v4 @@ -50,6 +54,14 @@ jobs: - name: Start MySQL run: sudo systemctl start mysql.service + if: matrix.db-type == 'mysql' + + - name: Start PostgreSQL + run: | + sudo systemctl start postgresql.service + pg_isready + sudo -u postgres createuser -s -d -r -w runner + if: matrix.db-type == 'postgres' #- name: Setup MySQL # uses: mirromutth/mysql-action@v1.1 @@ -99,7 +111,7 @@ jobs: - name: Create DB run: php bin/console --env test doctrine:database:create --if-not-exists -n - if: matrix.db-type == 'mysql' + if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres' # Checkinf for existance is not supported for sqlite, so do it without it - name: Create DB From 8266f230d7f0e7260f849a1e488eb83b3f0e4298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 22:58:54 +0200 Subject: [PATCH 26/56] For debugging disable fast failing of phpunit tests --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e99434ff..31682d6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: php-versions: [ '8.1', '8.2', '8.3' ] db-type: [ 'mysql', 'sqlite', 'postgres' ] From 1c8b81ca2c407726b207e0f01ba498fdc100a1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 23:05:16 +0200 Subject: [PATCH 27/56] Run a SSH session in the github actions container for better debugging --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31682d6a..c751f631 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: sudo -u postgres createuser -s -d -r -w runner if: matrix.db-type == 'postgres' - #- name: Setup MySQL + #- name: Setup MySQL # uses: mirromutth/mysql-action@v1.1 # with: # mysql version: 5.7 @@ -154,5 +154,9 @@ jobs: env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ failure() }} + From be97ea08a2d4f56bb994e3d4adb07fd97155f5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 23:35:13 +0200 Subject: [PATCH 28/56] Specify a version number on the MySQL database URL, so that doctrine does not need to start a connection while cache clearing --- .github/workflows/tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c751f631..c963371a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Set Database env for MySQL - run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb" >> $GITHUB_ENV + run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0" >> $GITHUB_ENV if: matrix.db-type == 'mysql' - name: Set Database env for SQLite @@ -154,9 +154,5 @@ jobs: env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ failure() }} - From 0c1c46c0451d4b95f366fff3ee3dc043f72370c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 23:46:33 +0200 Subject: [PATCH 29/56] Fixed MySQL version number The 8.0 was to short to be detected as newer than 8.0.0. We need to specify the bugfix release to get proper results --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c963371a..92430775 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Set Database env for MySQL - run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0" >> $GITHUB_ENV + run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0.35" >> $GITHUB_ENV if: matrix.db-type == 'mysql' - name: Set Database env for SQLite From 853e29dd833ea2d3a25ecf7733f2eac7d48b7707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 10 Jun 2024 23:47:28 +0200 Subject: [PATCH 30/56] Removed sqlite database creation This is not possible anymore for the sqlite driver and is unnesecarry as it is automatically created on migration --- .github/workflows/tests.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92430775..67f1b6d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,11 +113,6 @@ jobs: - name: Create DB run: php bin/console --env test doctrine:database:create --if-not-exists -n if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres' - - # Checkinf for existance is not supported for sqlite, so do it without it - - name: Create DB - run: php bin/console --env test doctrine:database:create -n - if: matrix.db-type == 'sqlite' - name: Do migrations run: php bin/console --env test doctrine:migrations:migrate -n From d7eadd929479d0e3bbb9aa29d9795ef9ac351d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 12 Jun 2024 23:13:20 +0200 Subject: [PATCH 31/56] Use the builtin postgres user already defined in the github image It hopefully requires no password --- .github/workflows/tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 67f1b6d1..02b1ce92 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: if: matrix.db-type == 'sqlite' - name: Set Database env for PostgreSQL - run: echo "DATABASE_URL=postgresql://runner:@127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV + run: echo "DATABASE_URL=postgresql://postgres:@127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV if: matrix.db-type == 'postgres' - name: Checkout @@ -60,8 +60,6 @@ jobs: - name: Start PostgreSQL run: | sudo systemctl start postgresql.service - pg_isready - sudo -u postgres createuser -s -d -r -w runner if: matrix.db-type == 'postgres' #- name: Setup MySQL From 58dd56a89feafed16a706a39d0eb6827f4b9bd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 12 Jun 2024 23:16:24 +0200 Subject: [PATCH 32/56] Supply a password for the postgres database connection --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02b1ce92..00d22215 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: if: matrix.db-type == 'sqlite' - name: Set Database env for PostgreSQL - run: echo "DATABASE_URL=postgresql://postgres:@127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV + run: echo "DATABASE_URL=postgresql://postgres:postgres @127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV if: matrix.db-type == 'postgres' - name: Checkout From 373a1ab0f421cd7ba29f7d565e7c80f61d2983d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 12 Jun 2024 23:24:37 +0200 Subject: [PATCH 33/56] Change the password of the postgres user --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 00d22215..3dc5de95 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,6 +60,7 @@ jobs: - name: Start PostgreSQL run: | sudo systemctl start postgresql.service + sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" if: matrix.db-type == 'postgres' #- name: Setup MySQL From d3c9b7eae10a75669511f7ff8137ada5d75785b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 12 Jun 2024 23:30:42 +0200 Subject: [PATCH 34/56] Configure postgres to accept all connections without password for testing --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3dc5de95..d154e3b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,6 +59,7 @@ jobs: - name: Start PostgreSQL run: | + sudo "echo 'host all all" >> /etc/postgresql/16/main/pg_hba.conf" sudo systemctl start postgresql.service sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" if: matrix.db-type == 'postgres' From d41996b3659ee512b0503a7af95d2bee24cf4b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 12 Jun 2024 23:33:05 +0200 Subject: [PATCH 35/56] Added SSH connection into test container for debugging --- .github/workflows/tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d154e3b1..eb78b5e8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -149,5 +149,6 @@ jobs: env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db - - + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ failure() }} From 07f1ce5822f5be127bba798412f6d5f404b2465a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 13 Jun 2024 00:00:51 +0200 Subject: [PATCH 36/56] Configure the postgres server to accept local connections without password --- .github/workflows/tests.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index eb78b5e8..9015607b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,9 +57,10 @@ jobs: run: sudo systemctl start mysql.service if: matrix.db-type == 'mysql' + # Replace the scram-sha-256 with trust for host connections, to avoid password authentication - name: Start PostgreSQL run: | - sudo "echo 'host all all" >> /etc/postgresql/16/main/pg_hba.conf" + sudo sed -i 's/^\(host.*all.*all.*\)scram-sha-256/\1trust/' /etc/postgresql/14/main/pg_hba.conf sudo systemctl start postgresql.service sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" if: matrix.db-type == 'postgres' @@ -148,7 +149,3 @@ jobs: if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2' env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db - - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ failure() }} From 33a5e70b707b4125502b01f2c7e67ba2bcf2ab2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 13 Jun 2024 22:19:17 +0200 Subject: [PATCH 37/56] Fixed phpunit tests for postgres --- src/Controller/ProjectController.php | 5 +++++ src/Repository/DBElementRepository.php | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 9068e7c2..9afdb645 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -207,6 +207,11 @@ class ProjectController extends AbstractController //Preset the BOM entries with the selected parts, when the form was not submitted yet $preset_data = new ArrayCollection(); foreach (explode(',', (string) $request->get('parts', '')) as $part_id) { + //Skip empty part IDs. Postgres seems to be especially sensitive to empty strings, as it does not allow them in integer columns + if ($part_id === '') { + continue; + } + $part = $entityManager->getRepository(Part::class)->find($part_id); if (null !== $part) { //If there is already a BOM entry for this part, we use this one (we edit it then) diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index e2e60b07..90bd5927 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -79,7 +79,7 @@ class DBElementRepository extends EntityRepository /** * Find all elements that match a list of IDs. - * + * They are ordered by IDs in an ascending order. * @return AbstractDBElement[] * @phpstan-return list */ @@ -89,6 +89,7 @@ class DBElementRepository extends EntityRepository $q = $qb->select('element') ->where('element.id IN (?1)') ->setParameter(1, $ids) + ->orderBy('element.id', 'ASC') ->getQuery(); return $q->getResult(); From c58ff5861db99657d19a314908e603d772b0e6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 13 Jun 2024 23:01:53 +0200 Subject: [PATCH 38/56] Replaced the non standard IFNULL function which postgres does not know with the COALSCE function --- config/packages/doctrine.yaml | 1 - src/DataTables/PartsDataTable.php | 2 +- tests/DatatablesAvailabilityTest.php | 17 +++++++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 8dac4552..9bcf3103 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -44,7 +44,6 @@ doctrine: dql: string_functions: regexp: DoctrineExtensions\Query\Mysql\Regexp - ifnull: DoctrineExtensions\Query\Mysql\IfNull field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 67dfa771..06927d55 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -345,7 +345,7 @@ final class PartsDataTable implements DataTableTypeInterface //Calculate amount sum using a subquery, so we can filter and sort by it $builder->addSelect( '( - SELECT IFNULL(SUM(partLot.amount), 0.0) + SELECT COALESCE(SUM(partLot.amount), 0.0) FROM '.PartLot::class.' partLot WHERE partLot.part = part.id AND partLot.instock_unknown = false diff --git a/tests/DatatablesAvailabilityTest.php b/tests/DatatablesAvailabilityTest.php index 3cb34d9b..7c7d055b 100644 --- a/tests/DatatablesAvailabilityTest.php +++ b/tests/DatatablesAvailabilityTest.php @@ -48,7 +48,7 @@ class DatatablesAvailabilityTest extends WebTestCase /** * @dataProvider urlProvider */ - public function testDataTable(string $url): void + public function testDataTable(string $url, ?array $ordering = null): void { //We have localized routes $url = '/en'.$url; @@ -68,7 +68,14 @@ class DatatablesAvailabilityTest extends WebTestCase 'PHP_AUTH_PW' => 'test', ]); $client->catchExceptions(false); - $client->request('POST', $url, ['_dt' => 'dt']); + + $post = ['_dt' => 'dt']; + + if ($ordering) { + $post['order'] = $ordering; + } + + $client->request('POST', $url, $post); $this->assertTrue($client->getResponse()->isSuccessful()); $this->assertJson($client->getResponse()->getContent()); } @@ -93,4 +100,10 @@ class DatatablesAvailabilityTest extends WebTestCase yield ['/category/1/parts?part_filter%5Bname%5D%5Boperator%5D=%3D&part_filter%5Bname%5D%5Bvalue%5D=BC547&part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Btags%5D%5Boperator%5D=ANY&part_filter%5Btags%5D%5Bvalue%5D=Test&part_filter%5Bsubmit%5D=']; yield ['/category/1/parts?part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Bstorelocation%5D%5Boperator%5D=%3D&part_filter%5Bstorelocation%5D%5Bvalue%5D=1&part_filter%5BattachmentsCount%5D%5Boperator%5D=%3D&part_filter%5BattachmentsCount%5D%5Bvalue1%5D=3&part_filter%5Bsubmit%5D=']; } + + public function testOrdering(): void + { + //Amount ordering (which uses the dynamic amount calculation) + $this->testDataTable('/parts', [['column' => 9, 'dir' => 'asc']]); + } } From 6e9b337b49d642178a98afa28b7a3e42fd22f7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 13 Jun 2024 23:18:25 +0200 Subject: [PATCH 39/56] Fixed regex function for postgres --- config/packages/doctrine.yaml | 2 +- .../Filters/Constraints/TextConstraint.php | 2 +- src/DataTables/Filters/PartSearchFilter.php | 2 +- src/Doctrine/Functions/Regexp.php | 52 +++++++++++++++++++ tests/DatatablesAvailabilityTest.php | 3 ++ 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/Doctrine/Functions/Regexp.php diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 9bcf3103..4048f399 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -43,7 +43,7 @@ doctrine: dql: string_functions: - regexp: DoctrineExtensions\Query\Mysql\Regexp + regexp: App\Doctrine\Functions\Regexp field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 60f83328..186a4477 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -113,7 +113,7 @@ class TextConstraint extends AbstractConstraint //Regex is only supported on MySQL and needs a special function if ($this->operator === 'REGEX') { - $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = 1', $this->property, $this->identifier)); + $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = TRUE', $this->property, $this->identifier)); $queryBuilder->setParameter($this->identifier, $this->value); } } diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index 34dd2d7a..84dfe9b3 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -129,7 +129,7 @@ class PartSearchFilter implements FilterInterface //Convert the fields to search to a list of expressions $expressions = array_map(function (string $field): string { if ($this->regex) { - return sprintf("REGEXP(%s, :search_query) = 1", $field); + return sprintf("REGEXP(%s, :search_query) = TRUE", $field); } return sprintf("%s LIKE :search_query", $field); diff --git a/src/Doctrine/Functions/Regexp.php b/src/Doctrine/Functions/Regexp.php new file mode 100644 index 00000000..d7c6f1e7 --- /dev/null +++ b/src/Doctrine/Functions/Regexp.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\ORM\Query\SqlWalker; + +/** + * Similar to the regexp function, but with support for multi platform. + */ +class Regexp extends \DoctrineExtensions\Query\Mysql\Regexp +{ + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + // + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { + $operator = 'REGEXP'; + } elseif ($platform instanceof PostgreSQLPlatform) { + //Use the case-insensitive operator, to have the same behavior as MySQL + $operator = '~*'; + } else { + throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support regular expressions.'); + } + + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->regexp->dispatch($sqlWalker) . ')'; + } +} \ No newline at end of file diff --git a/tests/DatatablesAvailabilityTest.php b/tests/DatatablesAvailabilityTest.php index 7c7d055b..23bc6f92 100644 --- a/tests/DatatablesAvailabilityTest.php +++ b/tests/DatatablesAvailabilityTest.php @@ -99,6 +99,9 @@ class DatatablesAvailabilityTest extends WebTestCase //Test using filters yield ['/category/1/parts?part_filter%5Bname%5D%5Boperator%5D=%3D&part_filter%5Bname%5D%5Bvalue%5D=BC547&part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Btags%5D%5Boperator%5D=ANY&part_filter%5Btags%5D%5Bvalue%5D=Test&part_filter%5Bsubmit%5D=']; yield ['/category/1/parts?part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Bstorelocation%5D%5Boperator%5D=%3D&part_filter%5Bstorelocation%5D%5Bvalue%5D=1&part_filter%5BattachmentsCount%5D%5Boperator%5D=%3D&part_filter%5BattachmentsCount%5D%5Bvalue1%5D=3&part_filter%5Bsubmit%5D=']; + + //Test regex search + yield ['/parts/search?category=1&comment=1&description=1&ipn=1&keyword=test&mpn=1&name=1&ordernr=1®ex=1&storelocation=1&tags=1']; } public function testOrdering(): void From 7ad2fab53db4f26497e020d43fbb5991b016fa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 13 Jun 2024 23:41:35 +0200 Subject: [PATCH 40/56] Fixed exception related that Datetimeimmutables were used were doctrine expected mutable ones this prevented the saving of parts from info providers --- src/Entity/Attachments/Attachment.php | 4 ++-- src/Entity/Attachments/AttachmentType.php | 4 ++-- src/Entity/Base/AbstractCompany.php | 4 ++-- src/Entity/Base/TimestampTrait.php | 8 ++++---- src/Entity/LogSystem/AbstractLogEntry.php | 6 +++--- src/Entity/Parts/Category.php | 4 ++-- src/Entity/Parts/Footprint.php | 4 ++-- src/Entity/Parts/InfoProviderReference.php | 6 +++--- src/Entity/Parts/MeasurementUnit.php | 4 ++-- src/Entity/Parts/Part.php | 4 ++-- src/Entity/Parts/PartLot.php | 6 +++--- src/Entity/Parts/StorageLocation.php | 4 ++-- src/Entity/PriceInformations/Currency.php | 4 ++-- src/Entity/ProjectSystem/Project.php | 4 ++-- src/Entity/UserSystem/User.php | 10 +++++----- src/Entity/UserSystem/WebauthnKey.php | 2 +- 16 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index c940bba8..a53a8d41 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -185,9 +185,9 @@ abstract class Attachment extends AbstractNamedDBElement protected ?AttachmentType $attachment_type = null; #[Groups(['attachment:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['attachment:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; public function __construct() diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 58546e6a..214f0af1 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -134,9 +134,9 @@ class AttachmentType extends AbstractStructuralDBElement protected Collection $attachments_with_type; #[Groups(['attachment_type:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['attachment_type:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; public function __construct() diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index 0d5b3579..13e6010a 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -41,9 +41,9 @@ use Symfony\Component\Validator\Constraints as Assert; abstract class AbstractCompany extends AbstractPartsContainingDBElement { #[Groups(['company:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['company:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; /** * @var string The address of the company diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index e6fac441..94ddf236 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -34,20 +34,20 @@ use Symfony\Component\Serializer\Annotation\Groups; trait TimestampTrait { /** - * @var \DateTimeInterface|null the date when this element was modified the last time + * @var \DateTime|null the date when this element was modified the last time */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; /** - * @var \DateTimeInterface|null the date when this element was created + * @var \DateTime|null the date when this element was created */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; /** * Returns the last time when the element was modified. diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index f1c163c5..cbccbbf0 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -55,10 +55,10 @@ abstract class AbstractLogEntry extends AbstractDBElement #[ORM\Column(type: Types::STRING)] protected string $username = ''; - /** @var \DateTimeInterface The datetime the event associated with this log entry has occured + /** @var \DateTime The datetime the event associated with this log entry has occured */ #[ORM\Column(name: 'datetime', type: Types::DATETIME_MUTABLE)] - protected \DateTimeInterface $timestamp; + protected \DateTime $timestamp; /** * @var LogLevel The priority level of the associated level. 0 is highest, 7 lowest @@ -174,7 +174,7 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setTimestamp(\DateTimeInterface $timestamp): self + public function setTimestamp(\DateTime $timestamp): self { $this->timestamp = $timestamp; diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index ed62618c..ecd609ce 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -182,9 +182,9 @@ class Category extends AbstractPartsContainingDBElement protected Collection $parameters; #[Groups(['category:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['category:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDACategoryInfo::class)] diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 10d3c9db..02c93694 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -133,9 +133,9 @@ class Footprint extends AbstractPartsContainingDBElement protected Collection $parameters; #[Groups(['footprint:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['footprint:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDAFootprintInfo::class)] diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index ea0fae7f..305c1d9b 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -55,7 +55,7 @@ class InfoProviderReference #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] #[Groups(['provider_reference:read'])] - private ?\DateTimeInterface $last_updated = null; + private ?\DateTime $last_updated = null; /** * Constructing is forbidden from outside. @@ -139,7 +139,7 @@ class InfoProviderReference $ref->provider_key = $provider_key; $ref->provider_id = $provider_id; $ref->provider_url = $provider_url; - $ref->last_updated = new \DateTimeImmutable(); + $ref->last_updated = new \DateTime(); return $ref; } @@ -154,7 +154,7 @@ class InfoProviderReference $ref->provider_key = $dto->provider_key; $ref->provider_id = $dto->provider_id; $ref->provider_url = $dto->provider_url; - $ref->last_updated = new \DateTimeImmutable(); + $ref->last_updated = new \DateTime(); return $ref; } } \ No newline at end of file diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 3ff1427b..870ad374 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -155,9 +155,9 @@ class MeasurementUnit extends AbstractPartsContainingDBElement protected Collection $parameters; #[Groups(['measurement_unit:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['measurement_unit:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 325b143a..090eda8b 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -153,9 +153,9 @@ class Part extends AttachmentContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['part:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['part:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; public function __construct() diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 5a5ecb80..25b40d5a 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -105,13 +105,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named protected string $comment = ''; /** - * @var \DateTimeInterface|null Set a time until when the lot must be used. + * @var \DateTime|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_MUTABLE, nullable: true)] #[Year2038BugWorkaround] - protected ?\DateTimeInterface $expiration_date = null; + protected ?\DateTime $expiration_date = null; /** * @var StorageLocation|null The storelocation of this lot @@ -246,7 +246,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * * */ - public function setExpirationDate(?\DateTimeInterface $expiration_date): self + public function setExpirationDate(?\DateTime $expiration_date): self { $this->expiration_date = $expiration_date; diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 8dd22a8c..c88decac 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -169,9 +169,9 @@ class StorageLocation extends AbstractPartsContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['location:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['location:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; /******************************************************************************** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 4df27d14..4e9a9acc 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -155,9 +155,9 @@ class Currency extends AbstractStructuralDBElement protected Collection $pricedetails; #[Groups(['currency:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['currency:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; public function __construct() diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index d3a4b65c..e537984f 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -154,9 +154,9 @@ class Project extends AbstractStructuralDBElement protected Collection $parameters; #[Groups(['project:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; #[Groups(['project:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; /******************************************************************************** diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 6f2a8f25..7d8c181a 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -116,10 +116,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?int $id = null; #[Groups(['user:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTime $lastModified = null; #[Groups(['user:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTime $addedDate = null; /** * @var bool Determines if the user is disabled (user can not log in) @@ -317,10 +317,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?PermissionData $permissions = null; /** - * @var \DateTimeInterface|null the time until the password reset token is valid + * @var \DateTime|null the time until the password reset token is valid */ #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $pw_reset_expires = null; + protected ?\DateTime $pw_reset_expires = null; /** * @var bool True if the user was created by a SAML provider (and therefore cannot change its password) @@ -535,7 +535,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the datetime when the password reset token expires. */ - public function setPwResetExpires(\DateTimeInterface $pw_reset_expires): self + public function setPwResetExpires(\DateTime $pw_reset_expires): self { $this->pw_reset_expires = $pw_reset_expires; diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index abb77a96..68868fc6 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -51,7 +51,7 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable protected ?User $user = null; #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - protected ?\DateTimeInterface $last_time_used = null; + protected ?\DateTimeImmutable $last_time_used = null; public function getName(): string { From 6d1553e8d8310fbaa1a19ac6d03e0c86b07f12d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 16 Jun 2024 22:52:15 +0200 Subject: [PATCH 41/56] Fixed problem with datatable column sorting on postgresql --- src/DataTables/Adapters/TwoStepORMAdapter.php | 6 ++++++ src/DataTables/PartsDataTable.php | 21 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/DataTables/Adapters/TwoStepORMAdapter.php b/src/DataTables/Adapters/TwoStepORMAdapter.php index 27ccd5a3..e225ce2f 100644 --- a/src/DataTables/Adapters/TwoStepORMAdapter.php +++ b/src/DataTables/Adapters/TwoStepORMAdapter.php @@ -129,6 +129,12 @@ class TwoStepORMAdapter extends ORMAdapter $query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases)); } + protected function hasGroupByPart(string $identifier, array $gbList): bool + { + //Always return true, to fix the issue with the count query, when having mutliple group by parts + return true; + } + protected function getCount(QueryBuilder $queryBuilder, $identifier): int { if ($this->query_modifier !== null) { diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 06927d55..36da9eda 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -264,8 +264,8 @@ final class PartsDataTable implements DataTableTypeInterface ->addSelect('part.minamount AS HIDDEN minamount') ->from(Part::class, 'part') - //This must be the only group by, or the paginator will not work correctly - ->addGroupBy('part.id'); + //The other group by fields, are dynamically added by the addJoins method + ->addGroupBy('part'); } private function getDetailQuery(QueryBuilder $builder, array $filter_results): void @@ -356,35 +356,52 @@ final class PartsDataTable implements DataTableTypeInterface if (str_contains($dql, '_category')) { $builder->leftJoin('part.category', '_category'); + $builder->addGroupBy('_category'); } if (str_contains($dql, '_master_picture_attachment')) { $builder->leftJoin('part.master_picture_attachment', '_master_picture_attachment'); + $builder->addGroupBy('_master_picture_attachment'); } if (str_contains($dql, '_partLots') || str_contains($dql, '_storelocations')) { $builder->leftJoin('part.partLots', '_partLots'); $builder->leftJoin('_partLots.storage_location', '_storelocations'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_partLots'); + //$builder->addGroupBy('_storelocations'); } if (str_contains($dql, '_footprint')) { $builder->leftJoin('part.footprint', '_footprint'); + $builder->addGroupBy('_footprint'); } if (str_contains($dql, '_manufacturer')) { $builder->leftJoin('part.manufacturer', '_manufacturer'); + $builder->addGroupBy('_manufacturer'); } if (str_contains($dql, '_orderdetails') || str_contains($dql, '_suppliers')) { $builder->leftJoin('part.orderdetails', '_orderdetails'); $builder->leftJoin('_orderdetails.supplier', '_suppliers'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_orderdetails'); + //$builder->addGroupBy('_suppliers'); } if (str_contains($dql, '_attachments')) { $builder->leftJoin('part.attachments', '_attachments'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_attachments'); } if (str_contains($dql, '_partUnit')) { $builder->leftJoin('part.partUnit', '_partUnit'); + $builder->addGroupBy('_partUnit'); } if (str_contains($dql, '_parameters')) { $builder->leftJoin('part.parameters', '_parameters'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_parameters'); } if (str_contains($dql, '_projectBomEntries')) { $builder->leftJoin('part.project_bom_entries', '_projectBomEntries'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_projectBomEntries'); } return $builder; From d3dcefb645f27f79e2e0ab660eb0e8c8618234f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 16 Jun 2024 23:26:57 +0200 Subject: [PATCH 42/56] Fixed total amount and less than desired filter on postgresql --- .../Constraints/Part/LessThanDesiredConstraint.php | 13 ++++++++++--- src/DataTables/Filters/PartFilter.php | 10 ++++++++-- tests/DatatablesAvailabilityTest.php | 4 ++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index eed37b8b..87292106 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -23,13 +23,20 @@ declare(strict_types=1); namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\BooleanConstraint; +use App\Entity\Parts\PartLot; use Doctrine\ORM\QueryBuilder; class LessThanDesiredConstraint extends BooleanConstraint { public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null) { - parent::__construct($property ?? 'amountSum', $identifier, $default_value); + parent::__construct($property ?? '( + SELECT COALESCE(SUM(ld_partLot.amount), 0.0) + FROM '.PartLot::class.' ld_partLot + WHERE ld_partLot.part = part.id + AND ld_partLot.instock_unknown = false + AND (ld_partLot.expiration_date IS NULL OR ld_partLot.expiration_date > CURRENT_DATE()) + )', $identifier ?? 'amountSumLessThanDesired', $default_value); } public function apply(QueryBuilder $queryBuilder): void @@ -41,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving('amountSum < minamount'); + $queryBuilder->andHaving( $this->property . ' < minamount'); } else { - $queryBuilder->andHaving('amountSum >= minamount'); + $queryBuilder->andHaving($this->property . ' >= minamount'); } } } diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 177b6dbd..ff98c76f 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -37,6 +37,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\ProjectSystem\Project; @@ -123,8 +124,13 @@ class PartFilter implements FilterInterface This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here */ - //We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error - $this->amountSum = (new IntConstraint('amountSum'))->useHaving(); + $this->amountSum = (new IntConstraint('( + SELECT COALESCE(SUM(__partLot.amount), 0.0) + FROM '.PartLot::class.' __partLot + WHERE __partLot.part = part.id + AND __partLot.instock_unknown = false + AND (__partLot.expiration_date IS NULL OR __partLot.expiration_date > CURRENT_DATE()) + )', identifier: "amountSumWhere")); $this->lotCount = new IntConstraint('COUNT(_partLots)'); $this->lessThanDesired = new LessThanDesiredConstraint(); diff --git a/tests/DatatablesAvailabilityTest.php b/tests/DatatablesAvailabilityTest.php index 23bc6f92..5af04627 100644 --- a/tests/DatatablesAvailabilityTest.php +++ b/tests/DatatablesAvailabilityTest.php @@ -99,6 +99,10 @@ class DatatablesAvailabilityTest extends WebTestCase //Test using filters yield ['/category/1/parts?part_filter%5Bname%5D%5Boperator%5D=%3D&part_filter%5Bname%5D%5Bvalue%5D=BC547&part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Btags%5D%5Boperator%5D=ANY&part_filter%5Btags%5D%5Bvalue%5D=Test&part_filter%5Bsubmit%5D=']; yield ['/category/1/parts?part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Bstorelocation%5D%5Boperator%5D=%3D&part_filter%5Bstorelocation%5D%5Bvalue%5D=1&part_filter%5BattachmentsCount%5D%5Boperator%5D=%3D&part_filter%5BattachmentsCount%5D%5Bvalue1%5D=3&part_filter%5Bsubmit%5D=']; + //Filter over total amount + yield ['/parts?part_filter%5BamountSum%5D%5Boperator%5D=>&part_filter%5BamountSum%5D%5Bvalue1%5D=1&part_filter%5Bsubmit%5D=']; + //Less than desired filter: + yield ['/parts?part_filter%5BlessThanDesired%5D%5Bvalue%5D=true&part_filter%5Bsubmit%5D=']; //Test regex search yield ['/parts/search?category=1&comment=1&description=1&ipn=1&keyword=test&mpn=1&name=1&ordernr=1®ex=1&storelocation=1&tags=1']; From 4f75e2641b7241fb4ab6ee4beb8c463f609a7061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 16 Jun 2024 23:46:40 +0200 Subject: [PATCH 43/56] Define a custom FIELD function to sort tables by list of ids without the emulation via string operations --- migrations/Version20240606203053.php | 9 +++++++++ src/Doctrine/Helpers/FieldHelper.php | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index bf382bbb..b23bea9b 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -199,6 +199,15 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + //Create the FIELD() function for PostgreSQL + $this->addSql(<<getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_ADMIN); diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index 49dc1475..d1c9eee6 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Doctrine\Helpers; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\QueryBuilder; /** @@ -44,11 +45,11 @@ final class FieldHelper { $db_platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); - //If we are on MySQL, we can just use the FIELD function - if ($db_platform instanceof AbstractMySQLPlatform) { + //If we are on MySQL, we can just use the FIELD function, for PostgreSQL we can use our custom defined one + if ($db_platform instanceof AbstractMySQLPlatform || $db_platform instanceof PostgreSQLPlatform) { $param = (is_numeric($bound_param) ? '?' : ":") . (string) $bound_param; $qb->orderBy("FIELD($field_expr, $param)", $order); - } else { + } else { //Use the sqlite/portable version //Retrieve the values from the bound parameter $param = $qb->getParameter($bound_param); if ($param === null) { From d7a7e22e5a57c9ea9c3c36723d8f3cb84842f03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 21:16:46 +0200 Subject: [PATCH 44/56] Fixed lessThanDesired Constraint for postgresql --- .../Filters/Constraints/Part/LessThanDesiredConstraint.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index 87292106..eb96ad33 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -48,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving( $this->property . ' < minamount'); + $queryBuilder->andHaving( $this->property . ' < part.minamount'); } else { - $queryBuilder->andHaving($this->property . ' >= minamount'); + $queryBuilder->andHaving($this->property . ' >= part.minamount'); } } } From 8a42dfa154ff9f20652a71465b1c9899f7c09b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 21:20:23 +0200 Subject: [PATCH 45/56] Use the FIELD function on postgres for order by field value --- src/Doctrine/Helpers/FieldHelper.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index d1c9eee6..d8654af6 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -87,13 +87,11 @@ final class FieldHelper $key = 'field2_' . md5($field_expr); - //If we are on MySQL, we can just use the FIELD function - if ($db_platform instanceof AbstractMySQLPlatform) { + //If we are on MySQL, we can just use the FIELD function, for postgres we can use our custom defined one + if ($db_platform instanceof AbstractMySQLPlatform || $db_platform instanceof PostgreSQLPlatform) { $qb->orderBy("FIELD($field_expr, :field_arr)", $order); } else { - //Generate a unique key from the field_expr - - //Otherwise we have to it using the FIELD2 function + //Otherwise use the portable version using string concatenation self::addSqliteOrderBy($qb, $field_expr, $key, $values, $order); } From 9db822eabd36e4e602f28574fbed668d6672bdde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 21:38:16 +0200 Subject: [PATCH 46/56] Use natural sorting for string datatables columns when using postgres The natural sorting solution is quite portable, so this should be possible for other database types too later --- config/packages/doctrine.yaml | 1 + migrations/Version20240606203053.php | 3 + src/DataTables/AttachmentDataTable.php | 2 + src/DataTables/LogDataTable.php | 2 +- src/DataTables/PartsDataTable.php | 14 ++-- src/DataTables/ProjectBomEntriesDataTable.php | 10 +-- src/Doctrine/Functions/Natsort.php | 65 +++++++++++++++++++ 7 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 src/Doctrine/Functions/Natsort.php diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 4048f399..451c8630 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -46,6 +46,7 @@ doctrine: regexp: App\Doctrine\Functions\Regexp field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 + natsort: App\Doctrine\Functions\Natsort when@test: doctrine: diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index b23bea9b..df94814b 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -24,6 +24,9 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function postgreSQLUp(Schema $schema): void { + //Create a collation for natural sorting + $this->addSql("CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');"); + $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 70995209..a53d61c9 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -86,6 +86,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', + 'orderField' => 'NATSORT(attachment.name)', 'render' => function ($value, Attachment $context) { //Link to external source if ($context->isExternal()) { @@ -111,6 +112,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('attachment_type', TextColumn::class, [ 'label' => 'attachment.table.type', 'field' => 'attachment_type.name', + 'orderField' => 'NATSORT(attachment_type.name)', 'render' => fn($value, Attachment $context): string => sprintf( '%s', $this->entityURLGenerator->editURL($context->getAttachmentType()), diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index b63fc2d7..b14c1b1f 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -154,7 +154,7 @@ class LogDataTable implements DataTableTypeInterface $dataTable->add('user', TextColumn::class, [ 'label' => 'log.user', - 'orderField' => 'user.name', + 'orderField' => 'NATSORT(user.name)', 'render' => function ($value, AbstractLogEntry $context): string { $user = $context->getUser(); diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 36da9eda..e04546ef 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -108,36 +108,40 @@ final class PartsDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context), + 'orderField' => 'NATSORT(part.name)' ]) ->add('id', TextColumn::class, [ 'label' => $this->translator->trans('part.table.id'), ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), + 'orderField' => 'NATSORT(part.ipn)' ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), + 'orderField' => 'NATSORT(part.description)' ]) ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'category', - 'orderField' => '_category.name' + 'orderField' => 'NATSORT(_category.name)' ]) ->add('footprint', EntityColumn::class, [ 'property' => 'footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => '_footprint.name' + 'orderField' => 'NATSORT(_footprint.name)' ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => '_manufacturer.name' + 'orderField' => 'NATSORT(_manufacturer.name)' ]) ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), - 'orderField' => '_storelocations.name', + 'orderField' => 'NATSORT(_storelocations.name)', 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), ], alias: 'storage_location') + ->add('amount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.amount'), 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context), @@ -151,7 +155,7 @@ final class PartsDataTable implements DataTableTypeInterface ->add('partUnit', TextColumn::class, [ 'field' => 'partUnit.name', 'label' => $this->translator->trans('part.table.partUnit'), - 'orderField' => '_partUnit.name' + 'orderField' => 'NATSORT(_partUnit.name)' ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index ffe166f3..c59ca3bd 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -82,7 +82,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), - 'orderField' => 'part.name', + 'orderField' => 'NATSORT(part.name)', 'render' => function ($value, ProjectBOMEntry $context) { if(!$context->getPart() instanceof Part) { return htmlspecialchars($context->getName()); @@ -101,7 +101,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), - 'orderField' => 'part.ipn', + 'orderField' => 'NATSORT(part.ipn)', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { if($context->getPart() instanceof Part) { @@ -124,18 +124,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'part.category', - 'orderField' => 'category.name', + 'orderField' => 'NATSORT(category.name)', ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => 'footprint.name', + 'orderField' => 'NATSORT(footprint.name)', ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'part.manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => 'manufacturer.name', + 'orderField' => 'NATSORT(manufacturer.name)', ]) ->add('mountnames', TextColumn::class, [ diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php new file mode 100644 index 00000000..51aeb01b --- /dev/null +++ b/src/Doctrine/Functions/Natsort.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class Natsort extends FunctionNode +{ + private Node $field; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + if ($platform instanceof AbstractPostgreSQLDriver) { + return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; + } + + if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) { + + } + + //For every other platform, return the field as is + return $this->field->dispatch($sqlWalker); + } + + +} \ No newline at end of file From 8bb8118d9fb2431942233718f8769e8e28f19b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 22:33:40 +0200 Subject: [PATCH 47/56] Use natural sorting for trees and others repository functions --- migrations/Version20240606203053.php | 2 +- src/Doctrine/Functions/Natsort.php | 11 +++++--- .../PartsContainingRepositoryInterface.php | 4 +-- .../AbstractPartsContainingRepository.php | 15 +++++++--- src/Repository/NamedDBElementRepository.php | 12 +++++--- src/Repository/PartRepository.php | 2 +- src/Repository/Parts/CategoryRepository.php | 4 +-- src/Repository/Parts/FootprintRepository.php | 4 +-- .../Parts/ManufacturerRepository.php | 4 +-- .../Parts/MeasurementUnitRepository.php | 4 +-- .../Parts/StorelocationRepository.php | 15 +++------- src/Repository/Parts/SupplierRepository.php | 10 +++---- .../StructuralDBElementRepository.php | 28 +++++++++++++++++-- 13 files changed, 71 insertions(+), 44 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index df94814b..bf9cee96 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -25,7 +25,7 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function postgreSQLUp(Schema $schema): void { //Create a collation for natural sorting - $this->addSql("CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');"); + $this->addSql("CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');"); $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php index 51aeb01b..fd4568f8 100644 --- a/src/Doctrine/Functions/Natsort.php +++ b/src/Doctrine/Functions/Natsort.php @@ -25,6 +25,7 @@ namespace App\Doctrine\Functions; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; @@ -33,7 +34,7 @@ use Doctrine\ORM\Query\TokenType; class Natsort extends FunctionNode { - private Node $field; + private ?Node $field = null; public function parse(Parser $parser): void { @@ -47,15 +48,17 @@ class Natsort extends FunctionNode public function getSql(SqlWalker $sqlWalker): string { + assert($this->field !== null, 'Field is not set'); + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); - if ($platform instanceof AbstractPostgreSQLDriver) { + if ($platform instanceof PostgreSQLPlatform) { return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; } - if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) { + /*if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) { - } + }*/ //For every other platform, return the field as is return $this->field->dispatch($sqlWalker); diff --git a/src/Entity/Base/PartsContainingRepositoryInterface.php b/src/Entity/Base/PartsContainingRepositoryInterface.php index f852bc35..89e7e5f6 100644 --- a/src/Entity/Base/PartsContainingRepositoryInterface.php +++ b/src/Entity/Base/PartsContainingRepositoryInterface.php @@ -30,11 +30,11 @@ interface PartsContainingRepositoryInterface * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. diff --git a/src/Repository/AbstractPartsContainingRepository.php b/src/Repository/AbstractPartsContainingRepository.php index e3c7f610..4fd0bbff 100644 --- a/src/Repository/AbstractPartsContainingRepository.php +++ b/src/Repository/AbstractPartsContainingRepository.php @@ -40,11 +40,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - abstract public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + abstract public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. @@ -113,7 +113,7 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo return $parts; } - protected function getPartsByField(object $element, array $order_by, string $field_name): array + protected function getPartsByField(object $element, string $nameOrderDirection, string $field_name): array { if (!$element instanceof AbstractPartsContainingDBElement) { throw new InvalidArgumentException('$element must be an instance of AbstractPartContainingDBElement!'); @@ -121,7 +121,14 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo $repo = $this->getEntityManager()->getRepository(Part::class); - return $repo->findBy([$field_name => $element], $order_by); + //Build a query builder to get the parts with a custom order by + + $qb = $repo->createQueryBuilder('part') + ->where('part.'.$field_name.' = :element') + ->setParameter('element', $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection); + + return $qb->getQuery()->getResult(); } protected function getPartsCountByField(object $element, string $field_name): int diff --git a/src/Repository/NamedDBElementRepository.php b/src/Repository/NamedDBElementRepository.php index ae8d4d8f..e62c64e7 100644 --- a/src/Repository/NamedDBElementRepository.php +++ b/src/Repository/NamedDBElementRepository.php @@ -42,7 +42,7 @@ class NamedDBElementRepository extends DBElementRepository { $result = []; - $entities = $this->findBy([], ['name' => 'ASC']); + $entities = $this->getFlatList(); foreach ($entities as $entity) { /** @var AbstractNamedDBElement $entity */ $node = new TreeViewNode($entity->getName(), null, null); @@ -65,13 +65,17 @@ class NamedDBElementRepository extends DBElementRepository } /** - * Returns a flattened list of all nodes. + * Returns a flattened list of all nodes, sorted by name in natural order. * @return AbstractNamedDBElement[] * @phpstan-return array */ public function getFlatList(): array { - //All nodes are sorted by name - return $this->findBy([], ['name' => 'ASC']); + $qb = $this->createQueryBuilder('e'); + $q = $qb->select('e') + ->orderBy('NATSORT(e.name)', 'ASC') + ->getQuery(); + + return $q->getResult(); } } diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 3ab3ed31..84357b72 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -90,7 +90,7 @@ class PartRepository extends NamedDBElementRepository $qb->setParameter('query', '%'.$query.'%'); $qb->setMaxResults($max_limits); - $qb->orderBy('part.name', 'ASC'); + $qb->orderBy('NATSORT(part.name)', 'ASC'); return $qb->getQuery()->getResult(); } diff --git a/src/Repository/Parts/CategoryRepository.php b/src/Repository/Parts/CategoryRepository.php index 8e270047..9bbb1e37 100644 --- a/src/Repository/Parts/CategoryRepository.php +++ b/src/Repository/Parts/CategoryRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class CategoryRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Category) { throw new InvalidArgumentException('$element must be an Category!'); } - return $this->getPartsByField($element, $order_by, 'category'); + return $this->getPartsByField($element, $nameOrderDirection, 'category'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/FootprintRepository.php b/src/Repository/Parts/FootprintRepository.php index 355cb1bb..8934831a 100644 --- a/src/Repository/Parts/FootprintRepository.php +++ b/src/Repository/Parts/FootprintRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class FootprintRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Footprint) { throw new InvalidArgumentException('$element must be an Footprint!'); } - return $this->getPartsByField($element, $order_by, 'footprint'); + return $this->getPartsByField($element, $nameOrderDirection, 'footprint'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/ManufacturerRepository.php b/src/Repository/Parts/ManufacturerRepository.php index a47142d4..1a838710 100644 --- a/src/Repository/Parts/ManufacturerRepository.php +++ b/src/Repository/Parts/ManufacturerRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class ManufacturerRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Manufacturer) { throw new InvalidArgumentException('$element must be an Manufacturer!'); } - return $this->getPartsByField($element, $order_by, 'manufacturer'); + return $this->getPartsByField($element, $nameOrderDirection, 'manufacturer'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/MeasurementUnitRepository.php b/src/Repository/Parts/MeasurementUnitRepository.php index 1c9b106b..c581f751 100644 --- a/src/Repository/Parts/MeasurementUnitRepository.php +++ b/src/Repository/Parts/MeasurementUnitRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class MeasurementUnitRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof MeasurementUnit) { throw new InvalidArgumentException('$element must be an MeasurementUnit!'); } - return $this->getPartsByField($element, $order_by, 'partUnit'); + return $this->getPartsByField($element, $nameOrderDirection, 'partUnit'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/StorelocationRepository.php b/src/Repository/Parts/StorelocationRepository.php index 192d4f6d..82317868 100644 --- a/src/Repository/Parts/StorelocationRepository.php +++ b/src/Repository/Parts/StorelocationRepository.php @@ -30,12 +30,7 @@ use InvalidArgumentException; class StorelocationRepository extends AbstractPartsContainingRepository { - /** - * @param object $element - * @param array $order_by - * @return array - */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); @@ -47,11 +42,9 @@ class StorelocationRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.partLots', 'lots') ->where('lots.storage_location = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } diff --git a/src/Repository/Parts/SupplierRepository.php b/src/Repository/Parts/SupplierRepository.php index 6dc995f1..393ae593 100644 --- a/src/Repository/Parts/SupplierRepository.php +++ b/src/Repository/Parts/SupplierRepository.php @@ -30,7 +30,7 @@ use InvalidArgumentException; class SupplierRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Supplier) { throw new InvalidArgumentException('$element must be an Supplier!'); @@ -42,11 +42,9 @@ class SupplierRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.orderdetails', 'orderdetail') ->where('orderdetail.supplier = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index 978cee20..c7548cc9 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -40,6 +40,28 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ private array $new_entity_cache = []; + /** + * Finds all nodes for the given parent node, ordered by name in a natural sort way + * @param AbstractStructuralDBElement|null $parent + * @param string $nameOrdering The ordering of the names. Either ASC or DESC + * @return array + */ + public function findNodesForParent(?AbstractStructuralDBElement $parent, string $nameOrdering = "ASC"): array + { + $qb = $this->createQueryBuilder('e'); + $qb->select('e') + ->orderBy('NATSORT(e.name)', $nameOrdering); + + if ($parent) { + $qb->where('e.parent = :parent') + ->setParameter('parent', $parent); + } else { + $qb->where('e.parent IS NULL'); + } + //@phpstan-ignore-next-line [parent is only defined by the sub classes] + return $qb->getQuery()->getResult(); + } + /** * Finds all nodes without a parent node. They are our root nodes. * @@ -47,7 +69,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ public function findRootNodes(): array { - return $this->findBy(['parent' => null], ['name' => 'ASC']); + return $this->findNodesForParent(null); } /** @@ -63,7 +85,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); foreach ($entities as $entity) { /** @var AbstractStructuralDBElement $entity */ //Make a recursive call to find all children nodes @@ -89,7 +111,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); $elementIterator = new StructuralDBElementIterator($entities); $recursiveIterator = new RecursiveIteratorIterator($elementIterator, RecursiveIteratorIterator::SELF_FIRST); From 0a482da93edab520fa1218483a27ebf39a7b3aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 23:13:04 +0200 Subject: [PATCH 48/56] Use postgres native array_position function instead of our FIELD function and pass it as array literal instead of variadic function Otherwise we will run into errors, that we can not give more than 100 arguments to a function --- config/packages/doctrine.yaml | 1 + migrations/Version20240606203053.php | 9 ---- src/Doctrine/Functions/ArrayPosition.php | 59 ++++++++++++++++++++++++ src/Doctrine/Helpers/FieldHelper.php | 38 +++++++++++---- 4 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 src/Doctrine/Functions/ArrayPosition.php diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 451c8630..07042a49 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -47,6 +47,7 @@ doctrine: field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 natsort: App\Doctrine\Functions\Natsort + array_position: App\Doctrine\Functions\ArrayPosition when@test: doctrine: diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index bf9cee96..f4bd28d0 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -202,15 +202,6 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - //Create the FIELD() function for PostgreSQL - $this->addSql(<<getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_ADMIN); diff --git a/src/Doctrine/Functions/ArrayPosition.php b/src/Doctrine/Functions/ArrayPosition.php new file mode 100644 index 00000000..39276912 --- /dev/null +++ b/src/Doctrine/Functions/ArrayPosition.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class ArrayPosition extends FunctionNode +{ + private ?Node $array = null; + + private ?Node $field = null; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->array = $parser->InParameter(); + + $parser->match(TokenType::T_COMMA); + + $this->field = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + return 'ARRAY_POSITION(' . + $this->array->dispatch($sqlWalker) . ', ' . + $this->field->dispatch($sqlWalker) . + ')'; + } +} \ No newline at end of file diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index d8654af6..d49df4e3 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -45,11 +45,11 @@ final class FieldHelper { $db_platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); - //If we are on MySQL, we can just use the FIELD function, for PostgreSQL we can use our custom defined one - if ($db_platform instanceof AbstractMySQLPlatform || $db_platform instanceof PostgreSQLPlatform) { - $param = (is_numeric($bound_param) ? '?' : ":") . (string) $bound_param; + //If we are on MySQL, we can just use the FIELD function + if ($db_platform instanceof AbstractMySQLPlatform ) { + $param = (is_numeric($bound_param) ? '?' : ":").(string)$bound_param; $qb->orderBy("FIELD($field_expr, $param)", $order); - } else { //Use the sqlite/portable version + } else { //Use the sqlite/portable version or postgresql //Retrieve the values from the bound parameter $param = $qb->getParameter($bound_param); if ($param === null) { @@ -58,12 +58,31 @@ final class FieldHelper //Generate a unique key from the field_expr $key = 'field2_' . (string) $bound_param; - self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + + if ($db_platform instanceof PostgreSQLPlatform) { + self::addPostgresOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } else { + self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } } return $qb; } + + private static function addPostgresOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void + { + //Use postgres native array_position function, to get the index of the value in the array + //In the end it gives a similar result as the FIELD function + $qb->orderBy("array_position(:$key, $field_expr)", $order); + + //Convert the values to a literal array, to overcome the problem of passing more than 100 parameters + $values = array_map(fn($value) => is_string($value) ? "'$value'" : $value, $values); + $literalArray = '{' . implode(',', $values) . '}'; + + $qb->setParameter($key, $literalArray); + } + private static function addSqliteOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void { //Otherwise we emulate it using @@ -87,9 +106,12 @@ final class FieldHelper $key = 'field2_' . md5($field_expr); - //If we are on MySQL, we can just use the FIELD function, for postgres we can use our custom defined one - if ($db_platform instanceof AbstractMySQLPlatform || $db_platform instanceof PostgreSQLPlatform) { - $qb->orderBy("FIELD($field_expr, :field_arr)", $order); + //If we are on MySQL, we can just use the FIELD function + if ($db_platform instanceof AbstractMySQLPlatform) { + $qb->orderBy("FIELD2($field_expr, :field_arr)", $order); + } else if ($db_platform instanceof PostgreSQLPlatform) { + //Use the postgres native array_position function + self::addPostgresOrderBy($qb, $field_expr, $key, $values, $order); } else { //Otherwise use the portable version using string concatenation self::addSqliteOrderBy($qb, $field_expr, $key, $values, $order); From 289c9126d026eb71f3bb13582e556a2b39cba9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 17 Jun 2024 23:29:19 +0200 Subject: [PATCH 49/56] Use Natural_SORT_KEY for natural sorting on MariaDB database which support that This resolves issue #243 and #402 --- src/Doctrine/Functions/Natsort.php | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php index fd4568f8..030c8dda 100644 --- a/src/Doctrine/Functions/Natsort.php +++ b/src/Doctrine/Functions/Natsort.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Doctrine\Functions; +use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; @@ -36,6 +37,29 @@ class Natsort extends FunctionNode { private ?Node $field = null; + private static ?bool $supportsNaturalSort = null; + + /** + * Check if the MariaDB version which is connected to supports the natural sort (meaning it has a version of 10.7.0 or higher) + * The result is cached in memory. + * @param Connection $connection + * @return bool + * @throws \Doctrine\DBAL\Exception + */ + private static function mariaDBSupportsNaturalSort(Connection $connection): bool + { + if (self::$supportsNaturalSort !== null) { + return self::$supportsNaturalSort; + } + + $version = $connection->getServerVersion(); + //Remove the -MariaDB suffix + $version = str_replace('-MariaDB', '', $version); + //We need at least MariaDB 10.7.0 to support the natural sort + self::$supportsNaturalSort = version_compare($version, '10.7.0', '>='); + return self::$supportsNaturalSort; + } + public function parse(Parser $parser): void { $parser->match(TokenType::T_IDENTIFIER); @@ -56,9 +80,9 @@ class Natsort extends FunctionNode return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; } - /*if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) { - - }*/ + if ($platform instanceof MariaDBPlatform && self::mariaDBSupportsNaturalSort($sqlWalker->getConnection())) { + return 'NATURAL_SORT_KEY(' . $this->field->dispatch($sqlWalker) . ')'; + } //For every other platform, return the field as is return $this->field->dispatch($sqlWalker); From 272fe0516b700d7991f432295864ced9d92d611f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 00:09:44 +0200 Subject: [PATCH 50/56] Allow to emulate natural sorting on SQLite databases --- .env | 4 ++ config/parameters.yaml | 4 ++ src/Doctrine/Functions/Natsort.php | 22 +++++++++ .../SQLiteRegexExtensionMiddlewareDriver.php | 5 ++ .../AllowSlowNaturalSortListener.php | 49 +++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 src/EventListener/AllowSlowNaturalSortListener.php diff --git a/.env b/.env index 8e48085e..b35b17f5 100644 --- a/.env +++ b/.env @@ -23,6 +23,10 @@ DATABASE_MYSQL_USE_SSL_CA=0 # Only do this, if you know what you are doing! DATABASE_MYSQL_SSL_VERIFY_CERT=1 +# Emulate natural sorting of strings even on databases that do not support it (like SQLite, MySQL or MariaDB < 10.7) +# This can be slow on big databases +DATABASE_EMULATE_NATURAL_SORT=0 + ################################################################################### # General settings ################################################################################### diff --git a/config/parameters.yaml b/config/parameters.yaml index 4caa5780..3c4b9c0a 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -16,6 +16,8 @@ parameters: partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails + partdb.db.emulate_natural_sort: '%env(bool:DATABASE_EMULATE_NATURAL_SORT)%' # If this is set to true, natural sorting is emulated on platforms that do not support it natively. This can be slow on large datasets. + ###################################################################################################################### # Users and Privacy ###################################################################################################################### @@ -145,3 +147,5 @@ parameters: env(HISTORY_SAVE_NEW_DATA): 1 env(EDA_KICAD_CATEGORY_DEPTH): 0 + + env(DATABASE_EMULATE_NATURAL_SORT): 0 diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php index 030c8dda..e133c23a 100644 --- a/src/Doctrine/Functions/Natsort.php +++ b/src/Doctrine/Functions/Natsort.php @@ -27,6 +27,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; @@ -39,6 +40,19 @@ class Natsort extends FunctionNode private static ?bool $supportsNaturalSort = null; + private static bool $allowSlowNaturalSort = false; + + /** + * As we can not inject parameters into the function, we use an event listener, to call the value on the static function. + * This is the only way to inject the value into the function. + * @param bool $allow + * @return void + */ + public static function allowSlowNaturalSort(bool $allow = true): void + { + self::$allowSlowNaturalSort = $allow; + } + /** * Check if the MariaDB version which is connected to supports the natural sort (meaning it has a version of 10.7.0 or higher) * The result is cached in memory. @@ -84,6 +98,14 @@ class Natsort extends FunctionNode return 'NATURAL_SORT_KEY(' . $this->field->dispatch($sqlWalker) . ')'; } + //Do the following operations only if we allow slow natural sort + if (self::$allowSlowNaturalSort) { + + if ($platform instanceof SQLitePlatform) { + return $this->field->dispatch($sqlWalker).' COLLATE NATURAL_CMP'; + } + } + //For every other platform, return the field as is return $this->field->dispatch($sqlWalker); } diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index 8421e818..c991d7b7 100644 --- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -48,6 +48,11 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC); + + //Create a new collation for natural sorting + if (method_exists($native_connection, 'sqliteCreateCollation')) { + $native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...)); + } } } diff --git a/src/EventListener/AllowSlowNaturalSortListener.php b/src/EventListener/AllowSlowNaturalSortListener.php new file mode 100644 index 00000000..02ec6144 --- /dev/null +++ b/src/EventListener/AllowSlowNaturalSortListener.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Doctrine\Functions\Natsort; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * This is a workaround to the fact that we can not inject parameters into doctrine custom functions. + * Therefore we use this event listener to call the static function on the custom function, to inject the value, before + * any NATSORT function is called. + */ +#[AsEventListener] +class AllowSlowNaturalSortListener +{ + public function __construct( + #[Autowire(param: 'partdb.db.emulate_natural_sort')] + private readonly bool $allowNaturalSort) + { + } + + public function __invoke(RequestEvent $event) + { + Natsort::allowSlowNaturalSort($this->allowNaturalSort); + } +} \ No newline at end of file From 123372d93fe93e9ab785ce9ae519f124af8a95a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 21:42:00 +0200 Subject: [PATCH 51/56] Allow to emulate natural sort on mysql platforms --- migrations/Version20240606203053.php | 116 +++++++++++++++++++++++++++ src/Doctrine/Functions/Natsort.php | 6 +- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index f4bd28d0..ec5d5d1f 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -342,6 +342,120 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('ALTER TABLE `users` CHANGE settings settings JSON NOT NULL, CHANGE backup_codes backup_codes JSON NOT NULL, CHANGE permissions_data permissions_data JSON NOT NULL'); $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL'); + // Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535) + $this->addSql(<<0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves). + Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n. + Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification. + Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign. + Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001). + Numbers with leading zeros sort after the same number with no (or fewer) leading zeros. + Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character. + Numbers with thousand separators sort after the same number without them. + Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly. + (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers). + Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted. + The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11) + Note that The first number component in an entity like this is also permitted to contain thousand separators. + + To achieve this, numbers within the input string are prefixed and suffixed according to the following format: + - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number. + - A 3-character suffix is appended after the number (after the decimals if present). + - The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number. + - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25) + - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise) + - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed. + Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence. + + e.g. here is how some simple sample strings get converted: + 'Foo055' --> 'Foo0255 02' + 'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade' + 'The $1,000,000 prize' --> 'The $071000000 01 prize' + '+99.74 degrees' --> '0299.74+00 degrees' + 'I have 0 apples' --> 'I have 00 02 apples' + '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08' + 'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004' + + The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal. + The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix. + A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789. + However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length. + + The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used. + This is to make the function code more portable. However, there are some important restrictions: + + - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work. + This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia). + To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use. + + - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters. + This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4). + If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH() + + Length of the output: + + Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert; + This parameter is provided as a means to limit the maximum output length (to input length + 5*n). + If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n. + Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2)) + So for the current input length of 100, the maximum output length is 350. + If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate. + ****/ + + DECLARE x,y varchar(1000); # need to be same length as input s + DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type + DECLARE suf varchar(101); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components) + DECLARE i,j,k int UNSIGNED; + IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers" + LOOP + SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit + IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done + SET n := n-1, suf := ' '; + IF i>1 THEN + IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control" + IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together + SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s) + END IF; + SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.' sequences) + SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8) + SET i := INSTR(x,'.'); + IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y + SET i := CHAR_LENGTH(x); + SET x := REPLACE(x,',',''); + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters + SET i := LOCATE('.',y,2); + IF i=0 THEN + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36 + ELSE # encode a version number (like 3.12.707, etc) + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + WHILE CHAR_LENGTH(y)>0 AND n!=0 DO + IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF; + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits + SET n := n-1; + END WHILE; + SET r := CONCAT(r,y,suf); + END IF; + END LOOP; + END$$ + DELIMITER ; + EOD + ); + } public function mySQLDown(Schema $schema): void @@ -357,6 +471,8 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('ALTER TABLE `users` CHANGE backup_codes backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE settings settings LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE transports transports LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', CHANGE trust_path trust_path LONGTEXT NOT NULL COMMENT \'(DC2Type:trust_path)\', CHANGE aaguid aaguid TINYTEXT NOT NULL COMMENT \'(DC2Type:aaguid)\', CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE other_ui other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\', CHANGE last_time_used last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + //Drop custom function + $this->addSql('DROP FUNCTION IF EXISTS NatSortKey'); } public function sqLiteUp(Schema $schema): void diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php index e133c23a..cf6fded9 100644 --- a/src/Doctrine/Functions/Natsort.php +++ b/src/Doctrine/Functions/Natsort.php @@ -25,6 +25,7 @@ namespace App\Doctrine\Functions; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLitePlatform; @@ -100,10 +101,13 @@ class Natsort extends FunctionNode //Do the following operations only if we allow slow natural sort if (self::$allowSlowNaturalSort) { - if ($platform instanceof SQLitePlatform) { return $this->field->dispatch($sqlWalker).' COLLATE NATURAL_CMP'; } + + if ($platform instanceof AbstractMySQLPlatform) { + return 'NatSortKey(' . $this->field->dispatch($sqlWalker) . ', 0)'; + } } //For every other platform, return the field as is From 4946a9ab0d529038a7cd5445e1be5222538c057a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 22:32:07 +0200 Subject: [PATCH 52/56] Fixed SQL declaration of NatSortKey function We do not need the delimiter declarations --- migrations/Version20240606203053.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index ec5d5d1f..9692e146 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -344,7 +344,6 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme // Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535) $this->addSql(<< Date: Tue, 18 Jun 2024 22:34:13 +0200 Subject: [PATCH 53/56] Do not natsort description fields as these can become very long --- src/DataTables/PartsDataTable.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index e04546ef..867032b7 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -119,7 +119,6 @@ final class PartsDataTable implements DataTableTypeInterface ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), - 'orderField' => 'NATSORT(part.description)' ]) ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), From 3f471d0c736282c06f3ed76809e3fd01cd0fe319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 22:34:29 +0200 Subject: [PATCH 54/56] Natsort MPN column in part datatables --- src/DataTables/PartsDataTable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 867032b7..da50fed3 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -181,6 +181,7 @@ final class PartsDataTable implements DataTableTypeInterface ]) ->add('manufacturer_product_number', TextColumn::class, [ 'label' => $this->translator->trans('part.table.mpn'), + 'orderField' => 'NATSORT(part.manufacturer_product_number)' ]) ->add('mass', SIUnitNumberColumn::class, [ 'label' => $this->translator->trans('part.table.mass'), From e3dfbf0e957bb7946db2a912fb15b558879ca123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 23:02:33 +0200 Subject: [PATCH 55/56] Support postgres in the backup command --- src/Command/BackupCommand.php | 36 ++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Command/BackupCommand.php b/src/Command/BackupCommand.php index e71713d0..bd647796 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Command; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Spatie\DbDumper\Databases\PostgreSql; use Symfony\Component\Console\Attribute\AsCommand; use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; @@ -136,30 +138,42 @@ class BackupCommand extends Command } } + private function runSQLDumper(DbDumper $dumper, ZipFile $zip, array $connectionParams): void + { + $this->configureDumper($connectionParams, $dumper); + + $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); + + $dumper->dumpToFile($tmp_file); + $zip->addFile($tmp_file, 'database.sql'); + } + protected function backupDatabase(ZipFile $zip, SymfonyStyle $io): void { $io->note('Backup database...'); //Determine if we use MySQL or SQLite $connection = $this->entityManager->getConnection(); - if ($connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + $params = $connection->getParams(); + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof AbstractMySQLPlatform) { try { $io->note('MySQL database detected. Dump DB to SQL using mysqldump...'); - $params = $connection->getParams(); - $dumper = MySql::create(); - $this->configureDumper($params, $dumper); - - $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); - - $dumper->dumpToFile($tmp_file); - $zip->addFile($tmp_file, 'mysql_dump.sql'); + $this->runSQLDumper(MySql::create(), $zip, $params); } catch (\Exception $e) { $io->error('Could not dump database: '.$e->getMessage()); $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!'); } - } elseif ($connection->getDatabasePlatform() instanceof SQLitePlatform) { + } elseif ($platform instanceof PostgreSQLPlatform) { + try { + $io->note('PostgreSQL database detected. Dump DB to SQL using pg_dump...'); + $this->runSQLDumper(PostgreSql::create(), $zip, $params); + } catch (\Exception $e) { + $io->error('Could not dump database: '.$e->getMessage()); + $io->error('This can maybe be fixed by installing the pg_dump binary and adding it to the PATH variable!'); + } + } elseif ($platform instanceof SQLitePlatform) { $io->note('SQLite database detected. Copy DB file to ZIP...'); - $params = $connection->getParams(); $zip->addFile($params['path'], 'var/app.db'); } else { $io->error('Unknown database platform. Could not backup database!'); From cb01302ada0c9c011c11c56ed301f7823655df3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 18 Jun 2024 23:04:44 +0200 Subject: [PATCH 56/56] Test the backup tool in github actions This ensures that the tool works for all database types --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9015607b..62bdc123 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -144,6 +144,9 @@ jobs: - name: Test check-requirements command run: php bin/console partdb:check-requirements -n + - name: Test partdb:backup command + run: php bin/console partdb:backup -n --full /tmp/test_backup.zip + - name: Test legacy Part-DB import run: bash .github/assets/legacy_import/test_legacy_import.sh if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2'