diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4497a..9216d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Latest * [#91](https://github.com/cleverage/ui-process-bundle/issues/91) `LoginController`: pass `error` and `last_username` (`AuthenticationUtils`) to the login template, so that a failed login displays the error message and keeps the email. Test updated. * [#95](https://github.com/cleverage/ui-process-bundle/issues/95) `DoctrineProcessHandler`: detach the written `LogRecord` entities after each flush (the Monolog records were detached instead), so that the identity map no longer grows during long processes; `LogRecord::$processExecution` cascade reduced from `all` to `persist`, so that detaching a log record does not detach the current process execution (which would then be inserted again). Add tests. * [#97](https://github.com/cleverage/ui-process-bundle/issues/97) Align the mapping and the schema created by the migrations: `ProcessSchedule::$input` mapped as `VARCHAR(255)` as created by the migrations (was `TEXT`); `Version20261005120000` migration (MySQL / MariaDB, PostgreSQL) making `log_record.process_execution_id` `NOT NULL` as in the mapping (log records without process execution are deleted). +* [#99](https://github.com/cleverage/ui-process-bundle/issues/99) Fix the migrations on PostgreSQL (they could not create a working schema): id columns created as identity columns (the sequences were not used: inserts failed with the IDENTITY generation), `process_schedule` created with the PostgreSQL syntax (`AUTO_INCREMENT` failed). PostgreSQL support documented. v3.0.2 ------ diff --git a/docs/index.md b/docs/index.md index 4ea04a2..a8d5b4d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -82,8 +82,11 @@ See [users & security](reference/03-users_and_security.md). ### Database -The bundle registers its own Doctrine migrations (`CleverAge\UiProcessBundle\Migrations`). Run them, then create a -first user: +Supported databases: **MySQL / MariaDB** and **PostgreSQL**. + +The bundle registers its own Doctrine migrations (`CleverAge\UiProcessBundle\Migrations`), written for these two +platforms (on another platform, e.g. SQLite, they do nothing: create the schema with `doctrine:schema:update`). Run +them, then create a first user: ```bash bin/console doctrine:migrations:migrate diff --git a/src/Migrations/Version20231006111525.php b/src/Migrations/Version20231006111525.php index 3d0a294..13a12db 100644 --- a/src/Migrations/Version20231006111525.php +++ b/src/Migrations/Version20231006111525.php @@ -46,16 +46,14 @@ public function up(Schema $schema): void if ($platform instanceof PostgreSQLPlatform) { if (!$schema->hasTable('log_record')) { - $this->addSql('CREATE SEQUENCE log_record_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE log_record (id INT NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE log_record (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, process_execution_id INT DEFAULT NULL, channel VARCHAR(64) NOT NULL, level INT NOT NULL, message VARCHAR(512) NOT NULL, context JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_8ECECC333DAC0075 ON log_record (process_execution_id)'); $this->addSql('CREATE INDEX idx_log_record_level ON log_record (level)'); $this->addSql('CREATE INDEX idx_log_record_created_at ON log_record (created_at)'); $this->addSql('COMMENT ON COLUMN log_record.created_at IS \'(DC2Type:datetime_immutable)\''); } if (!$schema->hasTable('process_execution')) { - $this->addSql('CREATE SEQUENCE process_execution_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE process_execution (id INT NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, status VARCHAR(255) NOT NULL, report JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE process_execution (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(255) NOT NULL, log_filename VARCHAR(255) NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, status VARCHAR(255) NOT NULL, report JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX idx_process_execution_code ON process_execution (code)'); $this->addSql('CREATE INDEX idx_process_execution_start_date ON process_execution (start_date)'); $this->addSql('COMMENT ON COLUMN process_execution.start_date IS \'(DC2Type:datetime_immutable)\''); @@ -63,8 +61,7 @@ public function up(Schema $schema): void $this->addSql('ALTER TABLE log_record ADD CONSTRAINT FK_8ECECC333DAC0075 FOREIGN KEY (process_execution_id) REFERENCES process_execution (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); } if (!$schema->hasTable('process_user')) { - $this->addSql('CREATE SEQUENCE process_user_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE process_user (id INT NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles JSON NOT NULL, password VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE process_user (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, email VARCHAR(255) NOT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, roles JSON NOT NULL, password VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)'); $this->addSql('CREATE INDEX idx_process_user_email ON process_user (email)'); } diff --git a/src/Migrations/Version20240729151928.php b/src/Migrations/Version20240729151928.php index be39996..9478097 100644 --- a/src/Migrations/Version20240729151928.php +++ b/src/Migrations/Version20240729151928.php @@ -34,7 +34,7 @@ public function up(Schema $schema): void { $platform = $this->connection->getDatabasePlatform(); if ($platform instanceof PostgreSQLPlatform) { - $this->addSql('CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE process_schedule (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255) DEFAULT NULL, context JSON NOT NULL, PRIMARY KEY(id))'); } if ($platform instanceof MariaDBPlatform || $platform instanceof MySQLPlatform) { diff --git a/tests/Migrations/MigrationTestCase.php b/tests/Migrations/MigrationTestCase.php deleted file mode 100644 index 4d30da7..0000000 --- a/tests/Migrations/MigrationTestCase.php +++ /dev/null @@ -1,87 +0,0 @@ - - */ - public static function provideMySqlPlatforms(): iterable - { - yield 'MySQL' => [new MySQLPlatform()]; - yield 'MariaDB' => [new MariaDBPlatform()]; - } - - /** - * @return iterable - */ - public static function providePlatforms(): iterable - { - yield from self::provideMySqlPlatforms(); - yield 'PostgreSQL' => [new PostgreSQLPlatform()]; - yield 'SQLite' => [new SQLitePlatform()]; - } - - /** - * @param class-string $class - */ - protected function createMigration(string $class, ?AbstractPlatform $platform = null): AbstractMigration - { - $connection = $this->createStub(Connection::class); - $connection->method('getDatabasePlatform')->willReturn($platform ?? new MySQLPlatform()); - - return new $class($connection, new NullLogger()); - } - - /** - * @return list - */ - protected function getStatements(AbstractMigration $migration): array - { - return array_values(array_map(static fn (Query $query): string => $query->getStatement(), $migration->getSql())); - } - - /** - * @param array> $tables table name => column names - */ - protected function createSchema(array $tables): Schema - { - $schema = new Schema(); - foreach ($tables as $name => $columns) { - $table = $schema->createTable($name); - $table->addColumn('id', 'integer'); - foreach ($columns as $column) { - $table->addColumn($column, 'string'); - } - } - - return $schema; - } -} diff --git a/tests/Migrations/Version20231006111525Test.php b/tests/Migrations/Version20231006111525Test.php deleted file mode 100644 index e756c3d..0000000 --- a/tests/Migrations/Version20231006111525Test.php +++ /dev/null @@ -1,131 +0,0 @@ -createMigration(Version20231006111525::class)->getDescription() - ); - } - - #[DataProvider('provideMySqlPlatforms')] - public function testUpOnMySql(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20231006111525::class, $platform); - $migration->up($this->createSchema([])); - - $statements = $this->getStatements($migration); - self::assertCount(4, $statements); - self::assertStringStartsWith('CREATE TABLE log_record (id INT AUTO_INCREMENT NOT NULL, process_execution_id INT DEFAULT NULL,', $statements[0]); - self::assertStringStartsWith('CREATE TABLE process_execution (id INT AUTO_INCREMENT NOT NULL, code VARCHAR(255) NOT NULL,', $statements[1]); - self::assertSame(self::FOREIGN_KEY, $statements[2]); - self::assertStringStartsWith('CREATE TABLE process_user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL,', $statements[3]); - self::assertStringContainsString('UNIQUE INDEX UNIQ_627A047CE7927C74 (email)', $statements[3]); - foreach ([0, 1, 3] as $index) { - self::assertStringEndsWith('DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $statements[$index]); - } - } - - public function testUpOnPostgreSql(): void - { - $migration = $this->createMigration(Version20231006111525::class, new PostgreSQLPlatform()); - $migration->up($this->createSchema([])); - - $statements = $this->getStatements($migration); - self::assertCount(17, $statements); - self::assertSame('CREATE SEQUENCE log_record_id_seq INCREMENT BY 1 MINVALUE 1 START 1', $statements[0]); - self::assertStringStartsWith('CREATE TABLE log_record (id INT NOT NULL,', $statements[1]); - self::assertSame('CREATE SEQUENCE process_execution_id_seq INCREMENT BY 1 MINVALUE 1 START 1', $statements[6]); - self::assertStringStartsWith('CREATE TABLE process_execution (id INT NOT NULL,', $statements[7]); - self::assertSame(self::FOREIGN_KEY.' NOT DEFERRABLE INITIALLY IMMEDIATE', $statements[12]); - self::assertSame('CREATE SEQUENCE process_user_id_seq INCREMENT BY 1 MINVALUE 1 START 1', $statements[13]); - self::assertStringStartsWith('CREATE TABLE process_user (id INT NOT NULL,', $statements[14]); - self::assertSame('CREATE UNIQUE INDEX UNIQ_627A047CE7927C74 ON process_user (email)', $statements[15]); - self::assertSame('CREATE INDEX idx_process_user_email ON process_user (email)', $statements[16]); - foreach ($statements as $statement) { - self::assertStringNotContainsString('AUTO_INCREMENT', $statement); - self::assertStringNotContainsString('ENGINE', $statement); - } - } - - #[DataProvider('providePlatforms')] - public function testUpDoesNothingWhenTheTablesExist(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20231006111525::class, $platform); - $migration->up($this->createSchema(['log_record' => [], 'process_execution' => [], 'process_user' => []])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('provideMySqlPlatforms')] - public function testUpOnlyCreatesMissingTablesOnMySql(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20231006111525::class, $platform); - $migration->up($this->createSchema(['log_record' => [], 'process_execution' => []])); - - $statements = $this->getStatements($migration); - self::assertCount(1, $statements); - self::assertStringStartsWith('CREATE TABLE process_user (', $statements[0]); - } - - public function testUpOnlyCreatesMissingTablesOnPostgreSql(): void - { - $migration = $this->createMigration(Version20231006111525::class, new PostgreSQLPlatform()); - $migration->up($this->createSchema(['log_record' => [], 'process_user' => []])); - - $statements = $this->getStatements($migration); - self::assertCount(7, $statements); - self::assertSame('CREATE SEQUENCE process_execution_id_seq INCREMENT BY 1 MINVALUE 1 START 1', $statements[0]); - self::assertSame(self::FOREIGN_KEY.' NOT DEFERRABLE INITIALLY IMMEDIATE', $statements[6]); - } - - public function testUpDoesNothingOnOtherPlatforms(): void - { - $migration = $this->createMigration(Version20231006111525::class, new SQLitePlatform()); - $migration->up($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDown(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20231006111525::class, $platform); - $migration->down($this->createSchema(['log_record' => [], 'process_execution' => [], 'process_user' => []])); - - self::assertSame( - [ - 'ALTER TABLE log_record DROP CONSTRAINT FK_8ECECC333DAC0075', - 'DROP TABLE log_record', - 'DROP TABLE process_execution', - 'DROP TABLE process_user', - ], - $this->getStatements($migration) - ); - } -} diff --git a/tests/Migrations/Version20240729151928Test.php b/tests/Migrations/Version20240729151928Test.php deleted file mode 100644 index e6be25f..0000000 --- a/tests/Migrations/Version20240729151928Test.php +++ /dev/null @@ -1,72 +0,0 @@ -createMigration(Version20240729151928::class)->getDescription()); - } - - #[DataProvider('provideMySqlPlatforms')] - public function testUpOnMySql(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20240729151928::class, $platform); - $migration->up($this->createSchema([])); - - $statements = $this->getStatements($migration); - self::assertCount(1, $statements); - self::assertStringStartsWith('CREATE TABLE process_schedule (', $statements[0]); - foreach (['process VARCHAR(255) NOT NULL', 'type VARCHAR(6) NOT NULL', 'expression VARCHAR(255) NOT NULL', 'input VARCHAR(255)', 'context JSON NOT NULL', 'ENGINE = InnoDB'] as $part) { - self::assertStringContainsString($part, $statements[0]); - } - } - - public function testUpOnPostgreSql(): void - { - $migration = $this->createMigration(Version20240729151928::class, new PostgreSQLPlatform()); - $migration->up($this->createSchema([])); - - self::assertSame( - ['CREATE TABLE process_schedule (id INT AUTO_INCREMENT NOT NULL, process VARCHAR(255) NOT NULL, type VARCHAR(6) NOT NULL, expression VARCHAR(255) NOT NULL, input VARCHAR(255), context JSON NOT NULL, PRIMARY KEY(id))'], - $this->getStatements($migration) - ); - } - - public function testUpDoesNothingOnOtherPlatforms(): void - { - $migration = $this->createMigration(Version20240729151928::class, new SQLitePlatform()); - $migration->up($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDown(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20240729151928::class, $platform); - $migration->down($this->createSchema(['process_schedule' => []])); - - self::assertSame(['DROP TABLE process_schedule'], $this->getStatements($migration)); - } -} diff --git a/tests/Migrations/Version20240730090403Test.php b/tests/Migrations/Version20240730090403Test.php deleted file mode 100644 index f1875e8..0000000 --- a/tests/Migrations/Version20240730090403Test.php +++ /dev/null @@ -1,46 +0,0 @@ -createMigration(Version20240730090403::class)->getDescription()); - } - - #[DataProvider('providePlatforms')] - public function testUp(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20240730090403::class, $platform); - $migration->up($this->createSchema(['process_user' => []])); - - self::assertSame(['ALTER TABLE process_user ADD token VARCHAR(255) DEFAULT NULL'], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDown(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20240730090403::class, $platform); - $migration->down($this->createSchema(['process_user' => ['token']])); - - self::assertSame(['ALTER TABLE process_user DROP token'], $this->getStatements($migration)); - } -} diff --git a/tests/Migrations/Version20241007134542Test.php b/tests/Migrations/Version20241007134542Test.php deleted file mode 100644 index 29acca6..0000000 --- a/tests/Migrations/Version20241007134542Test.php +++ /dev/null @@ -1,78 +0,0 @@ -createMigration(Version20241007134542::class)->getDescription()); - } - - #[DataProvider('providePlatforms')] - public function testUpAddsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241007134542::class, $platform); - $migration->up($this->createSchema(['process_user' => []])); - - self::assertSame(['ALTER TABLE process_user ADD timezone VARCHAR(255) DEFAULT NULL'], $this->getStatements($migration)); - } - - public function testUpDoesNothingWhenTheColumnExists(): void - { - $migration = $this->createMigration(Version20241007134542::class); - $migration->up($this->createSchema(['process_user' => ['timezone']])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testUpDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241007134542::class); - $migration->up($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDownDropsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241007134542::class, $platform); - $migration->down($this->createSchema(['process_user' => ['timezone']])); - - self::assertSame(['ALTER TABLE process_user DROP timezone'], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheColumn(): void - { - $migration = $this->createMigration(Version20241007134542::class); - $migration->down($this->createSchema(['process_user' => []])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241007134542::class); - $migration->down($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } -} diff --git a/tests/Migrations/Version20241007152613Test.php b/tests/Migrations/Version20241007152613Test.php deleted file mode 100644 index 564b351..0000000 --- a/tests/Migrations/Version20241007152613Test.php +++ /dev/null @@ -1,78 +0,0 @@ -createMigration(Version20241007152613::class)->getDescription()); - } - - #[DataProvider('providePlatforms')] - public function testUpAddsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241007152613::class, $platform); - $migration->up($this->createSchema(['process_execution' => []])); - - self::assertSame(['ALTER TABLE process_execution ADD context JSON NOT NULL'], $this->getStatements($migration)); - } - - public function testUpDoesNothingWhenTheColumnExists(): void - { - $migration = $this->createMigration(Version20241007152613::class); - $migration->up($this->createSchema(['process_execution' => ['context']])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testUpDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241007152613::class); - $migration->up($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDownDropsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241007152613::class, $platform); - $migration->down($this->createSchema(['process_execution' => ['context']])); - - self::assertSame(['ALTER TABLE process_execution DROP context'], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheColumn(): void - { - $migration = $this->createMigration(Version20241007152613::class); - $migration->down($this->createSchema(['process_execution' => []])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241007152613::class); - $migration->down($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } -} diff --git a/tests/Migrations/Version20241009075733Test.php b/tests/Migrations/Version20241009075733Test.php deleted file mode 100644 index bcfec99..0000000 --- a/tests/Migrations/Version20241009075733Test.php +++ /dev/null @@ -1,78 +0,0 @@ -createMigration(Version20241009075733::class)->getDescription()); - } - - #[DataProvider('providePlatforms')] - public function testUpAddsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241009075733::class, $platform); - $migration->up($this->createSchema(['process_user' => []])); - - self::assertSame(['ALTER TABLE process_user ADD locale VARCHAR(255) DEFAULT NULL'], $this->getStatements($migration)); - } - - public function testUpDoesNothingWhenTheColumnExists(): void - { - $migration = $this->createMigration(Version20241009075733::class); - $migration->up($this->createSchema(['process_user' => ['locale']])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testUpDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241009075733::class); - $migration->up($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } - - #[DataProvider('providePlatforms')] - public function testDownDropsTheColumn(AbstractPlatform $platform): void - { - $migration = $this->createMigration(Version20241009075733::class, $platform); - $migration->down($this->createSchema(['process_user' => ['locale']])); - - self::assertSame(['ALTER TABLE process_user DROP locale'], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheColumn(): void - { - $migration = $this->createMigration(Version20241009075733::class); - $migration->down($this->createSchema(['process_user' => []])); - - self::assertSame([], $this->getStatements($migration)); - } - - public function testDownDoesNothingWithoutTheTable(): void - { - $migration = $this->createMigration(Version20241009075733::class); - $migration->down($this->createSchema([])); - - self::assertSame([], $this->getStatements($migration)); - } -}