From b305d7274087a2e4ad46758c28f7738831fc7d49 Mon Sep 17 00:00:00 2001 From: Yassine Guedidi Date: Sun, 10 Mar 2024 17:30:29 +0100 Subject: [PATCH] Fix schema --- app/config/services.yml | 6 + migrations/Version20240310150000.php | 689 ++++++++++++++++++ src/Doctrine/WallabagMigration.php | 36 + src/Entity/Api/AccessToken.php | 16 - src/Entity/Api/AuthCode.php | 16 - src/Entity/Api/RefreshToken.php | 16 - src/Entity/Config.php | 2 +- src/Entity/Entry.php | 27 +- src/Entity/IgnoreOriginUserRule.php | 1 + src/Entity/InternalSetting.php | 14 +- src/Entity/SiteCredential.php | 7 +- src/Entity/Tag.php | 3 +- .../Subscriber/SchemaAdapterSubscriber.php | 68 ++ tests/bootstrap.php | 11 + 14 files changed, 829 insertions(+), 83 deletions(-) create mode 100644 migrations/Version20240310150000.php create mode 100644 src/Event/Subscriber/SchemaAdapterSubscriber.php diff --git a/app/config/services.yml b/app/config/services.yml index 90d574aeb..d29ef44b2 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -174,6 +174,12 @@ services: tags: - { name: doctrine.event_subscriber } + Wallabag\Event\Subscriber\SchemaAdapterSubscriber: + arguments: + $databaseTablePrefix: "%database_table_prefix%" + tags: + - { name: doctrine.event_subscriber } + Graby\Graby: arguments: $config: diff --git a/migrations/Version20240310150000.php b/migrations/Version20240310150000.php new file mode 100644 index 000000000..370674349 --- /dev/null +++ b/migrations/Version20240310150000.php @@ -0,0 +1,689 @@ +platform instanceof MySQLPlatform: + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('annotation') . ' WHERE text IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('annotation') . '" with null value on "text" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('entry') . ' WHERE is_not_parsed IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('entry') . '" with null value on "is_not_parsed" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('tag') . ' WHERE label IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('tag') . '" with null value on "label" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('tag') . ' WHERE slug IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('tag') . '" with null value on "slug" column.'); + break; + case $this->platform instanceof PostgreSQLPlatform: + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('entry') . ' WHERE is_not_parsed IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('entry') . '" with null value on "is_not_parsed" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('ignore_origin_user_rule') . ' WHERE id IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('ignore_origin_user_rule') . '" with null value on "id" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('ignore_origin_instance_rule') . ' WHERE id IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('ignore_origin_instance_rule') . '" with null value on "id" column.'); + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('site_credential') . ' WHERE id IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('site_credential') . '" with null value on "id" column.'); + break; + case $this->platform instanceof SqlitePlatform: + $this->abortIf($this->connection->executeQuery('SELECT COUNT(*) FROM ' . $this->getTable('entry') . ' WHERE is_not_parsed IS NULL')->fetchOne() > 0, 'There are rows in the table "' . $this->getTable('entry') . '" with null value on "is_not_parsed" column.'); + break; + } + + switch (true) { + case $this->platform instanceof MySQLPlatform: + $this->write('Align database schema with Doctrine metadata.'); + + $this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' CHANGE name name LONGTEXT NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE salt salt VARCHAR(255) DEFAULT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE password password VARCHAR(255) NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE googleAuthenticatorSecret googleAuthenticatorSecret VARCHAR(255) DEFAULT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE published_by published_by LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\';'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE headers headers LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\';'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE hashed_url hashed_url VARCHAR(40) DEFAULT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE hashed_given_url hashed_given_url VARCHAR(40) DEFAULT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE is_not_parsed is_not_parsed TINYINT(1) DEFAULT 0 NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;'); + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CHANGE label label LONGTEXT NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CHANGE slug slug VARCHAR(128) NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('annotation') . ' CHANGE text text LONGTEXT NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('annotation') . ' CHANGE quote quote LONGTEXT NOT NULL;'); + break; + case $this->platform instanceof PostgreSQLPlatform: + $this->write('Align database schema with Doctrine metadata.'); + + $this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' ALTER name TYPE TEXT;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('ignore_origin_instance_rule') . ' ALTER id DROP DEFAULT;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER salt DROP NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER confirmation_token TYPE VARCHAR(180);'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER googleauthenticatorsecret TYPE VARCHAR(255);'); + + $this->addSql('COMMENT ON COLUMN ' . $this->getTable('entry') . '.published_by IS \'(DC2Type:array)\';'); + $this->addSql('COMMENT ON COLUMN ' . $this->getTable('entry') . '.headers IS \'(DC2Type:array)\';'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER hashed_url TYPE VARCHAR(40);'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER hashed_given_url TYPE VARCHAR(40);'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER is_not_parsed SET NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('site_credential') . ' ALTER id DROP DEFAULT;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('ignore_origin_user_rule') . ' ALTER id DROP DEFAULT;'); + break; + } + + if ($this->platform instanceof MySQLPlatform || $this->platform instanceof PostgreSQLPlatform) { + $this->write('Synchronize indexes and foreign keys with the default naming convention.'); + + $this->renameForeignKey('annotation', 'FK_A7AED006A76ED395', 'user', 'user_id'); + $this->renameForeignKey('annotation', 'FK_annotation_entry', 'entry', 'entry_id', true); + $this->renameForeignKey('config', 'FK_87E64C53A76ED395', 'user', 'user_id'); + $this->renameForeignKey('entry', 'FK_F4D18282A76ED395', 'user', 'user_id'); + $this->renameForeignKey('entry_tag', 'FK_entry_tag_entry', 'entry', 'entry_id', true); + $this->renameForeignKey('entry_tag', 'FK_entry_tag_tag', 'tag', 'tag_id', true); + $this->renameForeignKey('ignore_origin_user_rule', 'fk_config', 'config', 'config_id'); + $this->renameForeignKey('oauth2_access_tokens', 'FK_368A4209A76ED395', 'user', 'user_id', true); + $this->renameForeignKey('oauth2_access_tokens', 'FK_368A420919EB6921', 'oauth2_clients', 'client_id'); + $this->renameForeignKey('oauth2_auth_codes', 'FK_EE52E3FA19EB6921', 'oauth2_clients', 'client_id'); + $this->renameForeignKey('oauth2_auth_codes', 'FK_EE52E3FAA76ED395', 'user', 'user_id', true); + $this->renameForeignKey('oauth2_clients', 'FK_635D765EA76ED395', 'user', 'user_id'); + $this->renameForeignKey('oauth2_refresh_tokens', 'FK_20C9FB24A76ED395', 'user', 'user_id', true); + $this->renameForeignKey('oauth2_refresh_tokens', 'FK_20C9FB2419EB6921', 'oauth2_clients', 'client_id'); + $this->renameForeignKey('site_credential', 'fk_user', 'user', 'user_id'); + $this->renameForeignKey('tagging_rule', 'FK_2D9B3C5424DB0683', 'config', 'config_id'); + + $this->renameIndex('annotation', 'idx_a7aed006a76ed395', 'user_id'); + $this->renameIndex('annotation', 'idx_a7aed006ba364942', 'entry_id'); + $this->renameIndex('config', 'config_feed_token', 'feed_token'); + $this->renameIndex('entry', 'hashed_given_url_user_id', ['user_id', 'hashed_given_url']); + $this->renameIndex('entry', 'hashed_url_user_id', ['user_id', 'hashed_url']); + $this->renameIndex('entry', 'idx_entry_created_at', 'created_at'); + $this->renameIndex('entry', 'idx_entry_uid', 'uid'); + $this->renameIndex('entry', 'idx_f4d18282a76ed395', 'user_id'); + $this->renameIndex('entry', 'user_archived', ['user_id', 'is_archived', 'archived_at']); + $this->renameIndex('entry', 'user_created', ['user_id', 'created_at']); + $this->renameIndex('entry', 'user_language', ['language', 'user_id']); + $this->renameIndex('entry', 'user_starred', ['user_id', 'is_starred', 'starred_at']); + $this->renameIndex('entry_tag', 'idx_c9f0dd7cba364942', 'entry_id'); + $this->renameIndex('entry_tag', 'idx_c9f0dd7cbad26311', 'tag_id'); + $this->renameIndex('ignore_origin_user_rule', 'idx_config', 'config_id'); + $this->renameIndex('oauth2_access_tokens', 'idx_368a4209a76ed395', 'user_id'); + $this->renameIndex('oauth2_access_tokens', 'idx_368a420919eb6921', 'client_id'); + $this->renameIndex('oauth2_auth_codes', 'IDX_EE52E3FA19EB6921', 'client_id'); + $this->renameIndex('oauth2_auth_codes', 'IDX_EE52E3FAA76ED395', 'user_id'); + $this->renameIndex('oauth2_refresh_tokens', 'idx_20c9fb24a76ed395', 'user_id'); + $this->renameIndex('oauth2_refresh_tokens', 'idx_20c9fb2419eb6921', 'client_id'); + $this->renameIndex('site_credential', 'idx_user', 'user_id'); + $this->renameIndex('tag', 'tag_label', 'label'); + $this->renameIndex('tagging_rule', 'idx_2d9b3c5424db0683', 'config_id'); + + $this->renameUniqueIndex('config', 'uniq_87e64c53a76ed395', 'user_id'); + $this->renameUniqueIndex('tag', 'uniq_4ca58a8c989d9b62', 'slug'); + $this->renameUniqueIndex('user', 'uniq_1d63e7e5a0d96fbf', 'email_canonical'); + $this->renameUniqueIndex('user', 'uniq_1d63e7e5c05fb297', 'confirmation_token'); + $this->renameUniqueIndex('user', 'uniq_1d63e7e592fc23a8', 'username_canonical'); + $this->renameUniqueIndex('oauth2_access_tokens', 'UNIQ_368A42095F37A13B', 'token'); + $this->renameUniqueIndex('oauth2_auth_codes', 'UNIQ_EE52E3FA5F37A13B', 'token'); + $this->renameUniqueIndex('oauth2_refresh_tokens', 'UNIQ_20C9FB245F37A13B', 'token'); + + $this->dropIndex('entry', 'is_archived', 'IDX_entry_archived'); + $this->dropIndex('entry', 'is_starred', 'IDX_entry_starred'); + + $this->dropUniqueIndex('internal_setting', 'name', 'UNIQ_5D9649505E237E06'); + } + + if ($this->platform instanceof SqlitePlatform) { + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_annotation AS SELECT id, user_id, entry_id, text, created_at, updated_at, quote, ranges FROM ' . $this->getTable('annotation')); + $this->addSql('DROP TABLE ' . $this->getTable('annotation')); + $this->addSql('CREATE TABLE ' . $this->getTable('annotation') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, entry_id INTEGER DEFAULT NULL, text CLOB NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, quote CLOB NOT NULL, ranges CLOB NOT NULL --(DC2Type:array) + , CONSTRAINT ' . $this->getForeignKeyName('annotation', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('annotation', 'entry_id') . ' FOREIGN KEY (entry_id) REFERENCES ' . $this->getTable('entry') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('annotation') . ' (id, user_id, entry_id, text, created_at, updated_at, quote, ranges) SELECT id, user_id, entry_id, text, created_at, updated_at, quote, ranges FROM __temp__wallabag_annotation'); + $this->addSql('DROP TABLE __temp__wallabag_annotation'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('annotation', 'user_id') . ' ON ' . $this->getTable('annotation') . ' (user_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('annotation', 'entry_id') . ' ON ' . $this->getTable('annotation') . ' (entry_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_config AS SELECT id, user_id, items_per_page, language, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, feed_token, feed_limit, display_thumbnails, custom_css, font, fontsize, line_height, max_width FROM ' . $this->getTable('config')); + $this->addSql('DROP TABLE ' . $this->getTable('config')); + $this->addSql('CREATE TABLE ' . $this->getTable('config') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, items_per_page INTEGER NOT NULL, language VARCHAR(255) NOT NULL, reading_speed DOUBLE PRECISION DEFAULT NULL, pocket_consumer_key VARCHAR(255) DEFAULT NULL, action_mark_as_read INTEGER DEFAULT 0, list_mode INTEGER DEFAULT NULL, feed_token VARCHAR(255) DEFAULT NULL, feed_limit INTEGER DEFAULT NULL, display_thumbnails INTEGER DEFAULT 1, custom_css CLOB DEFAULT NULL, font CLOB DEFAULT NULL, fontsize DOUBLE PRECISION DEFAULT NULL, line_height DOUBLE PRECISION DEFAULT NULL, max_width DOUBLE PRECISION DEFAULT NULL, CONSTRAINT ' . $this->getForeignKeyName('config', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('config') . ' (id, user_id, items_per_page, language, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, feed_token, feed_limit, display_thumbnails, custom_css, font, fontsize, line_height, max_width) SELECT id, user_id, items_per_page, language, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, feed_token, feed_limit, display_thumbnails, custom_css, font, fontsize, line_height, max_width FROM __temp__wallabag_config'); + $this->addSql('DROP TABLE __temp__wallabag_config'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('config', 'user_id') . ' ON ' . $this->getTable('config') . ' (user_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('config', 'feed_token') . ' ON ' . $this->getTable('config') . ' (feed_token)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_entry AS SELECT id, user_id, title, url, is_archived, is_starred, content, created_at, updated_at, mimetype, domain_name, preview_picture, uid, http_status, published_at, starred_at, origin_url, archived_at, given_url, reading_time, published_by, headers, hashed_url, hashed_given_url, language, is_not_parsed FROM ' . $this->getTable('entry')); + $this->addSql('DROP TABLE ' . $this->getTable('entry')); + $this->addSql('CREATE TABLE ' . $this->getTable('entry') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, title CLOB DEFAULT NULL, url CLOB DEFAULT NULL, is_archived BOOLEAN NOT NULL, is_starred BOOLEAN NOT NULL, content CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, mimetype CLOB DEFAULT NULL, domain_name CLOB DEFAULT NULL, preview_picture CLOB DEFAULT NULL, uid VARCHAR(23) DEFAULT NULL, http_status VARCHAR(3) DEFAULT NULL, published_at DATETIME DEFAULT NULL, starred_at DATETIME DEFAULT NULL, origin_url CLOB DEFAULT NULL, archived_at DATETIME DEFAULT NULL, given_url CLOB DEFAULT NULL, reading_time INTEGER NOT NULL, published_by CLOB DEFAULT NULL --(DC2Type:array) + , headers CLOB DEFAULT NULL --(DC2Type:array) + , hashed_url VARCHAR(40) DEFAULT NULL, hashed_given_url VARCHAR(40) DEFAULT NULL, language VARCHAR(20) DEFAULT NULL, is_not_parsed BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT ' . $this->getForeignKeyName('entry', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('entry') . ' (id, user_id, title, url, is_archived, is_starred, content, created_at, updated_at, mimetype, domain_name, preview_picture, uid, http_status, published_at, starred_at, origin_url, archived_at, given_url, reading_time, published_by, headers, hashed_url, hashed_given_url, language, is_not_parsed) SELECT id, user_id, title, url, is_archived, is_starred, content, created_at, updated_at, mimetype, domain_name, preview_picture, uid, http_status, published_at, starred_at, origin_url, archived_at, given_url, reading_time, published_by, headers, hashed_url, hashed_given_url, language, is_not_parsed FROM __temp__wallabag_entry'); + $this->addSql('DROP TABLE __temp__wallabag_entry'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', 'user_id') . ' ON ' . $this->getTable('entry') . ' (user_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', 'created_at') . ' ON ' . $this->getTable('entry') . ' (created_at)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', 'uid') . ' ON ' . $this->getTable('entry') . ' (uid)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['user_id', 'hashed_url']) . ' ON ' . $this->getTable('entry') . ' (user_id, hashed_url)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['user_id', 'hashed_given_url']) . ' ON ' . $this->getTable('entry') . ' (user_id, hashed_given_url)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['language', 'user_id']) . ' ON ' . $this->getTable('entry') . ' (language, user_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['user_id', 'is_archived', 'archived_at']) . ' ON ' . $this->getTable('entry') . ' (user_id, is_archived, archived_at)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['user_id', 'created_at']) . ' ON ' . $this->getTable('entry') . ' (user_id, created_at)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry', ['user_id', 'is_starred', 'starred_at']) . ' ON ' . $this->getTable('entry') . ' (user_id, is_starred, starred_at)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_entry_tag AS SELECT entry_id, tag_id FROM ' . $this->getTable('entry_tag')); + $this->addSql('DROP TABLE ' . $this->getTable('entry_tag')); + $this->addSql('CREATE TABLE ' . $this->getTable('entry_tag') . ' (entry_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY(entry_id, tag_id), CONSTRAINT ' . $this->getForeignKeyName('entry_tag', 'entry_id') . ' FOREIGN KEY (entry_id) REFERENCES ' . $this->getTable('entry') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('entry_tag', 'tag_id') . ' FOREIGN KEY (tag_id) REFERENCES ' . $this->getTable('tag') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('entry_tag') . ' (entry_id, tag_id) SELECT entry_id, tag_id FROM __temp__wallabag_entry_tag'); + $this->addSql('DROP TABLE __temp__wallabag_entry_tag'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry_tag', 'tag_id') . ' ON ' . $this->getTable('entry_tag') . ' (tag_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('entry_tag', 'entry_id') . ' ON ' . $this->getTable('entry_tag') . ' (entry_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_ignore_origin_user_rule AS SELECT id, config_id, rule FROM ' . $this->getTable('ignore_origin_user_rule')); + $this->addSql('DROP TABLE ' . $this->getTable('ignore_origin_user_rule')); + $this->addSql('CREATE TABLE ' . $this->getTable('ignore_origin_user_rule') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, config_id INTEGER NOT NULL, rule VARCHAR(255) NOT NULL, CONSTRAINT ' . $this->getForeignKeyName('ignore_origin_user_rule', 'config_id') . ' FOREIGN KEY (config_id) REFERENCES ' . $this->getTable('config') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('ignore_origin_user_rule') . ' (id, config_id, rule) SELECT id, config_id, rule FROM __temp__wallabag_ignore_origin_user_rule'); + $this->addSql('DROP TABLE __temp__wallabag_ignore_origin_user_rule'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('ignore_origin_user_rule', 'config_id') . ' ON ' . $this->getTable('ignore_origin_user_rule') . ' (config_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_internal_setting AS SELECT name, value, section FROM ' . $this->getTable('internal_setting')); + $this->addSql('DROP TABLE ' . $this->getTable('internal_setting')); + $this->addSql('CREATE TABLE ' . $this->getTable('internal_setting') . ' (name VARCHAR(255) NOT NULL, value VARCHAR(255) DEFAULT NULL, section VARCHAR(255) DEFAULT NULL, PRIMARY KEY(name))'); + $this->addSql('INSERT INTO ' . $this->getTable('internal_setting') . ' (name, value, section) SELECT name, value, section FROM __temp__wallabag_internal_setting'); + $this->addSql('DROP TABLE __temp__wallabag_internal_setting'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_access_tokens AS SELECT id, client_id, user_id, expires_at, token, scope FROM ' . $this->getTable('oauth2_access_tokens')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_access_tokens')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_access_tokens') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(255) NOT NULL, scope VARCHAR(255) DEFAULT NULL, CONSTRAINT ' . $this->getForeignKeyName('oauth2_access_tokens', 'client_id') . ' FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('oauth2_access_tokens', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_access_tokens') . ' (id, client_id, user_id, expires_at, token, scope) SELECT id, client_id, user_id, expires_at, token, scope FROM __temp__wallabag_oauth2_access_tokens'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_access_tokens'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_access_tokens', 'client_id') . ' ON ' . $this->getTable('oauth2_access_tokens') . ' (client_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_access_tokens', 'user_id') . ' ON ' . $this->getTable('oauth2_access_tokens') . ' (user_id)'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('oauth2_access_tokens', 'token') . ' ON ' . $this->getTable('oauth2_access_tokens') . ' (token)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_auth_codes AS SELECT id, client_id, user_id, redirect_uri, expires_at, token, scope FROM ' . $this->getTable('oauth2_auth_codes')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_auth_codes')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_auth_codes') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, redirect_uri CLOB NOT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(255) NOT NULL, scope VARCHAR(255) DEFAULT NULL, CONSTRAINT ' . $this->getForeignKeyName('oauth2_auth_codes', 'client_id') . ' FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('oauth2_auth_codes', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_auth_codes') . ' (id, client_id, user_id, redirect_uri, expires_at, token, scope) SELECT id, client_id, user_id, redirect_uri, expires_at, token, scope FROM __temp__wallabag_oauth2_auth_codes'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_auth_codes'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_auth_codes', 'client_id') . ' ON ' . $this->getTable('oauth2_auth_codes') . ' (client_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_auth_codes', 'user_id') . ' ON ' . $this->getTable('oauth2_auth_codes') . ' (user_id)'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('oauth2_auth_codes', 'token') . ' ON ' . $this->getTable('oauth2_auth_codes') . ' (token)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_clients AS SELECT id, user_id, random_id, secret, name, redirect_uris, allowed_grant_types FROM ' . $this->getTable('oauth2_clients')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_clients')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_clients') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, random_id VARCHAR(255) NOT NULL, secret VARCHAR(255) NOT NULL, name CLOB NOT NULL, redirect_uris CLOB NOT NULL --(DC2Type:array) + , allowed_grant_types CLOB NOT NULL --(DC2Type:array) + , CONSTRAINT ' . $this->getForeignKeyName('oauth2_clients', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_clients') . ' (id, user_id, random_id, secret, name, redirect_uris, allowed_grant_types) SELECT id, user_id, random_id, secret, name, redirect_uris, allowed_grant_types FROM __temp__wallabag_oauth2_clients'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_clients'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_clients', 'user_id') . ' ON ' . $this->getTable('oauth2_clients') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_refresh_tokens AS SELECT id, client_id, user_id, expires_at, token, scope FROM ' . $this->getTable('oauth2_refresh_tokens')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_refresh_tokens')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(255) NOT NULL, scope VARCHAR(255) DEFAULT NULL, CONSTRAINT ' . $this->getForeignKeyName('oauth2_refresh_tokens', 'client_id') . ' FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('oauth2_refresh_tokens', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_refresh_tokens') . ' (id, client_id, user_id, expires_at, token, scope) SELECT id, client_id, user_id, expires_at, token, scope FROM __temp__wallabag_oauth2_refresh_tokens'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_refresh_tokens'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_refresh_tokens', 'client_id') . ' ON ' . $this->getTable('oauth2_refresh_tokens') . ' (client_id)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('oauth2_refresh_tokens', 'user_id') . ' ON ' . $this->getTable('oauth2_refresh_tokens') . ' (user_id)'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('oauth2_refresh_tokens', 'token') . ' ON ' . $this->getTable('oauth2_refresh_tokens') . ' (token)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_site_credential AS SELECT id, user_id, host, username, password, createdAt, updated_at FROM ' . $this->getTable('site_credential')); + $this->addSql('DROP TABLE ' . $this->getTable('site_credential')); + $this->addSql('CREATE TABLE ' . $this->getTable('site_credential') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER NOT NULL, host VARCHAR(255) NOT NULL, username CLOB NOT NULL, password CLOB NOT NULL, createdAt DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, CONSTRAINT ' . $this->getForeignKeyName('site_credential', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('site_credential') . ' (id, user_id, host, username, password, createdAt, updated_at) SELECT id, user_id, host, username, password, createdAt, updated_at FROM __temp__wallabag_site_credential'); + $this->addSql('DROP TABLE __temp__wallabag_site_credential'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('site_credential', 'user_id') . ' ON ' . $this->getTable('site_credential') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_tag AS SELECT id, label, slug FROM ' . $this->getTable('tag')); + $this->addSql('DROP TABLE ' . $this->getTable('tag')); + $this->addSql('CREATE TABLE ' . $this->getTable('tag') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, label CLOB NOT NULL, slug VARCHAR(128) NOT NULL)'); + $this->addSql('INSERT INTO ' . $this->getTable('tag') . ' (id, label, slug) SELECT id, label, slug FROM __temp__wallabag_tag'); + $this->addSql('DROP TABLE __temp__wallabag_tag'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('tag', 'slug') . ' ON ' . $this->getTable('tag') . ' (slug)'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('tag', 'label') . ' ON ' . $this->getTable('tag') . ' (label)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_tagging_rule AS SELECT id, config_id, rule, tags FROM ' . $this->getTable('tagging_rule')); + $this->addSql('DROP TABLE ' . $this->getTable('tagging_rule')); + $this->addSql('CREATE TABLE ' . $this->getTable('tagging_rule') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, config_id INTEGER DEFAULT NULL, rule VARCHAR(255) NOT NULL, tags CLOB NOT NULL --(DC2Type:simple_array) + , CONSTRAINT ' . $this->getForeignKeyName('tagging_rule', 'config_id') . ' FOREIGN KEY (config_id) REFERENCES ' . $this->getTable('config') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('tagging_rule') . ' (id, config_id, rule, tags) SELECT id, config_id, rule, tags FROM __temp__wallabag_tagging_rule'); + $this->addSql('DROP TABLE __temp__wallabag_tagging_rule'); + $this->addSql('CREATE INDEX ' . $this->getIndexName('tagging_rule', 'config_id') . ' ON ' . $this->getTable('tagging_rule') . ' (config_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor FROM ' . $this->getTable('user')); + $this->addSql('DROP TABLE ' . $this->getTable('user')); + $this->addSql('CREATE TABLE ' . $this->getTable('user') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, username_canonical VARCHAR(180) NOT NULL, email VARCHAR(180) NOT NULL, email_canonical VARCHAR(180) NOT NULL, enabled BOOLEAN NOT NULL, salt VARCHAR(255) DEFAULT NULL, password VARCHAR(255) NOT NULL, last_login DATETIME DEFAULT NULL, confirmation_token VARCHAR(180) DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:array) + , name CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, googleAuthenticatorSecret VARCHAR(255) DEFAULT NULL, backupCodes CLOB DEFAULT NULL --(DC2Type:json) + , emailTwoFactor BOOLEAN NOT NULL)'); + $this->addSql('INSERT INTO ' . $this->getTable('user') . ' (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor FROM __temp__user'); + $this->addSql('DROP TABLE __temp__user'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('user', 'username_canonical') . ' ON ' . $this->getTable('user') . ' (username_canonical)'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('user', 'email_canonical') . ' ON ' . $this->getTable('user') . ' (email_canonical)'); + $this->addSql('CREATE UNIQUE INDEX ' . $this->getUniqueIndexName('user', 'confirmation_token') . ' ON ' . $this->getTable('user') . ' (confirmation_token)'); + } + } + + public function down(Schema $schema): void + { + switch (true) { + case $this->platform instanceof MySQLPlatform: + $this->write('Revert: Align database schema with Doctrine metadata.'); + + $this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' CHANGE name name LONGBLOB NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE salt salt VARCHAR(180) NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE password password VARCHAR(180) NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE googleAuthenticatorSecret googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE published_by published_by LONGTEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE headers headers LONGTEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE hashed_url hashed_url TINYTEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE hashed_given_url hashed_given_url TINYTEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' CHANGE is_not_parsed is_not_parsed TINYINT(1) DEFAULT 0;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'); + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CHANGE label label LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;'); + $this->addSql('ALTER TABLE ' . $this->getTable('tag') . ' CHANGE slug slug VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('annotation') . ' CHANGE text text LONGTEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('annotation') . ' CHANGE quote quote TEXT NOT NULL;'); + break; + case $this->platform instanceof PostgreSQLPlatform: + $this->write('Revert: Align database schema with Doctrine metadata.'); + + $this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' ALTER name TYPE BYTEA USING name::bytea;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('ignore_origin_instance_rule') . ' ALTER id SET DEFAULT nextval(\'' . $this->getTablePrefix() . 'ignore_origin_instance_rule_id_seq\');'); + + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER salt SET NOT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER confirmation_token TYPE VARCHAR(255);'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER confirmation_token SET DEFAULT NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER googleauthenticatorsecret TYPE VARCHAR(191);'); + $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ALTER googleauthenticatorsecret SET DEFAULT NULL;'); + + $this->addSql('COMMENT ON COLUMN ' . $this->getTable('entry') . '.published_by IS NULL;'); + $this->addSql('COMMENT ON COLUMN ' . $this->getTable('entry') . '.headers IS NULL;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER hashed_url TYPE TEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER hashed_given_url TYPE TEXT;'); + $this->addSql('ALTER TABLE ' . $this->getTable('entry') . ' ALTER is_not_parsed DROP NOT NULL;'); + + $this->addSql('ALTER TABLE ' . $this->getTable('site_credential') . ' ALTER id SET DEFAULT nextval(\'' . $this->getTablePrefix() . 'site_credential_id_seq\');'); + + $this->addSql('ALTER TABLE ' . $this->getTable('ignore_origin_user_rule') . ' ALTER id SET DEFAULT nextval(\'' . $this->getTablePrefix() . 'ignore_origin_user_rule_id_seq\');'); + break; + } + + if ($this->platform instanceof MySQLPlatform || $this->platform instanceof PostgreSQLPlatform) { + $this->write('Revert: Synchronize indexes and foreign keys with the default naming convention.'); + + $this->revertRenameForeignKey('annotation', 'FK_A7AED006A76ED395', 'user', 'user_id'); + $this->revertRenameForeignKey('annotation', 'FK_annotation_entry', 'entry', 'entry_id', true); + $this->revertRenameForeignKey('config', 'FK_87E64C53A76ED395', 'user', 'user_id'); + $this->revertRenameForeignKey('entry', 'FK_F4D18282A76ED395', 'user', 'user_id'); + $this->revertRenameForeignKey('entry_tag', 'FK_entry_tag_entry', 'entry', 'entry_id', true); + $this->revertRenameForeignKey('entry_tag', 'FK_entry_tag_tag', 'tag', 'tag_id', true); + $this->revertRenameForeignKey('ignore_origin_user_rule', 'fk_config', 'config', 'config_id'); + $this->revertRenameForeignKey('oauth2_access_tokens', 'FK_368A4209A76ED395', 'user', 'user_id', true); + $this->revertRenameForeignKey('oauth2_access_tokens', 'FK_368A420919EB6921', 'oauth2_clients', 'client_id'); + $this->revertRenameForeignKey('oauth2_auth_codes', 'FK_EE52E3FA19EB6921', 'oauth2_clients', 'client_id'); + $this->revertRenameForeignKey('oauth2_auth_codes', 'FK_EE52E3FAA76ED395', 'user', 'user_id', true); + $this->revertRenameForeignKey('oauth2_clients', 'FK_635D765EA76ED395', 'user', 'user_id'); + $this->revertRenameForeignKey('oauth2_refresh_tokens', 'FK_20C9FB24A76ED395', 'user', 'user_id', true); + $this->revertRenameForeignKey('oauth2_refresh_tokens', 'FK_20C9FB2419EB6921', 'oauth2_clients', 'client_id'); + $this->revertRenameForeignKey('site_credential', 'fk_user', 'user', 'user_id'); + $this->revertRenameForeignKey('tagging_rule', 'FK_2D9B3C5424DB0683', 'config', 'config_id'); + + $this->revertRenameIndex('annotation', 'idx_a7aed006a76ed395', 'user_id'); + $this->revertRenameIndex('annotation', 'idx_a7aed006ba364942', 'entry_id'); + $this->revertRenameIndex('config', 'config_feed_token', 'feed_token'); + $this->revertRenameIndex('entry', 'hashed_given_url_user_id', ['user_id', 'hashed_given_url']); + $this->revertRenameIndex('entry', 'hashed_url_user_id', ['user_id', 'hashed_url']); + $this->revertRenameIndex('entry', 'IDX_entry_created_at', 'created_at'); + $this->revertRenameIndex('entry', 'IDX_entry_uid', 'uid'); + $this->revertRenameIndex('entry', 'idx_f4d18282a76ed395', 'user_id'); + $this->revertRenameIndex('entry', 'user_archived', ['user_id', 'is_archived', 'archived_at']); + $this->revertRenameIndex('entry', 'user_created', ['user_id', 'created_at']); + $this->revertRenameIndex('entry', 'user_language', ['language', 'user_id']); + $this->revertRenameIndex('entry', 'user_starred', ['user_id', 'is_starred', 'starred_at']); + $this->revertRenameIndex('entry_tag', 'idx_c9f0dd7cba364942', 'entry_id'); + $this->revertRenameIndex('entry_tag', 'idx_c9f0dd7cbad26311', 'tag_id'); + $this->revertRenameIndex('ignore_origin_user_rule', 'idx_config', 'config_id'); + $this->revertRenameIndex('oauth2_access_tokens', 'idx_368a4209a76ed395', 'user_id'); + $this->revertRenameIndex('oauth2_access_tokens', 'idx_368a420919eb6921', 'client_id'); + $this->revertRenameIndex('oauth2_auth_codes', 'IDX_EE52E3FA19EB6921', 'client_id'); + $this->revertRenameIndex('oauth2_auth_codes', 'IDX_EE52E3FAA76ED395', 'user_id'); + $this->revertRenameIndex('oauth2_refresh_tokens', 'idx_20c9fb2419eb6921', 'client_id'); + $this->revertRenameIndex('oauth2_refresh_tokens', 'idx_20c9fb24a76ed395', 'user_id'); + $this->revertRenameIndex('site_credential', 'idx_user', 'user_id'); + $this->revertRenameIndex('tag', 'tag_label', 'label'); + $this->revertRenameIndex('tagging_rule', 'idx_2d9b3c5424db0683', 'config_id'); + + $this->revertRenameUniqueIndex('config', 'uniq_87e64c53a76ed395', 'user_id'); + $this->revertRenameUniqueIndex('tag', 'uniq_4ca58a8c989d9b62', 'slug'); + $this->revertRenameUniqueIndex('user', 'uniq_1d63e7e5a0d96fbf', 'email_canonical'); + $this->revertRenameUniqueIndex('user', 'uniq_1d63e7e5c05fb297', 'confirmation_token'); + $this->revertRenameUniqueIndex('user', 'uniq_1d63e7e592fc23a8', 'username_canonical'); + $this->revertRenameUniqueIndex('oauth2_access_tokens', 'UNIQ_368A42095F37A13B', 'token'); + $this->revertRenameUniqueIndex('oauth2_auth_codes', 'UNIQ_EE52E3FA5F37A13B', 'token'); + $this->revertRenameUniqueIndex('oauth2_refresh_tokens', 'UNIQ_20C9FB245F37A13B', 'token'); + + $this->createIndex('entry', 'is_archived', 'IDX_entry_archived'); + $this->createIndex('entry', 'is_starred', 'IDX_entry_starred'); + + $this->createUniqueIndex('internal_setting', 'name', 'UNIQ_5D9649505E237E06'); + } + + if ($this->platform instanceof SqlitePlatform) { + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_annotation AS SELECT id, user_id, entry_id, text, created_at, updated_at, quote, ranges FROM ' . $this->getTable('annotation')); + $this->addSql('DROP TABLE ' . $this->getTable('annotation')); + $this->addSql('CREATE TABLE ' . $this->getTable('annotation') . ' (id INTEGER PRIMARY KEY NOT NULL, user_id INTEGER DEFAULT NULL, entry_id INTEGER DEFAULT NULL, text CLOB NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, quote CLOB NOT NULL, ranges CLOB NOT NULL, CONSTRAINT ' . $this->getForeignKeyName('annotation', 'user_id') . ' FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT ' . $this->getForeignKeyName('annotation', 'entry_id') . ' FOREIGN KEY (entry_id) REFERENCES ' . $this->getTable('entry') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('annotation') . ' (id, user_id, entry_id, text, created_at, updated_at, quote, ranges) SELECT id, user_id, entry_id, text, created_at, updated_at, quote, ranges FROM __temp__wallabag_annotation'); + $this->addSql('DROP TABLE __temp__wallabag_annotation'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_config AS SELECT id, user_id, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, display_thumbnails, font, fontsize, line_height, max_width, custom_css FROM ' . $this->getTable('config')); + $this->addSql('DROP TABLE ' . $this->getTable('config')); + $this->addSql('CREATE TABLE ' . $this->getTable('config') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, items_per_page INTEGER NOT NULL, language VARCHAR(255) NOT NULL, reading_speed DOUBLE PRECISION DEFAULT NULL, pocket_consumer_key VARCHAR(255) DEFAULT NULL, action_mark_as_read INTEGER DEFAULT 0, list_mode INTEGER DEFAULT NULL, feed_token VARCHAR(255) DEFAULT NULL, feed_limit INTEGER DEFAULT NULL, display_thumbnails INTEGER DEFAULT 1, custom_css CLOB DEFAULT NULL, font CLOB DEFAULT NULL, fontsize DOUBLE PRECISION DEFAULT NULL, line_height DOUBLE PRECISION DEFAULT NULL, max_width DOUBLE PRECISION DEFAULT NULL, CONSTRAINT FK_87E64C53A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('config') . ' (id, user_id, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, display_thumbnails, font, fontsize, line_height, max_width, custom_css) SELECT id, user_id, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode, display_thumbnails, font, fontsize, line_height, max_width, custom_css FROM __temp__wallabag_config'); + $this->addSql('DROP TABLE __temp__wallabag_config'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_87E64C53A76ED395 ON ' . $this->getTable('config') . ' (user_id)'); + $this->addSql('CREATE INDEX config_feed_token ON ' . $this->getTable('config') . ' (feed_token)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_entry AS SELECT id, user_id, uid, title, url, hashed_url, origin_url, given_url, hashed_given_url, is_archived, archived_at, is_starred, content, created_at, updated_at, published_at, published_by, starred_at, mimetype, language, reading_time, domain_name, preview_picture, http_status, headers, is_not_parsed FROM ' . $this->getTable('entry')); + $this->addSql('DROP TABLE ' . $this->getTable('entry')); + $this->addSql('CREATE TABLE ' . $this->getTable('entry') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, title CLOB DEFAULT NULL, url CLOB DEFAULT NULL, is_archived BOOLEAN NOT NULL, is_starred BOOLEAN NOT NULL, content CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, mimetype CLOB DEFAULT NULL, domain_name CLOB DEFAULT NULL, preview_picture CLOB DEFAULT NULL, uid VARCHAR(23) DEFAULT NULL, http_status VARCHAR(3) DEFAULT NULL, published_at DATETIME DEFAULT NULL, starred_at DATETIME DEFAULT NULL, origin_url CLOB DEFAULT NULL, archived_at DATETIME DEFAULT NULL, given_url CLOB DEFAULT NULL, reading_time INTEGER NOT NULL, published_by CLOB DEFAULT NULL --(DC2Type:array) + , headers CLOB DEFAULT NULL --(DC2Type:array) + , hashed_url VARCHAR(40) DEFAULT NULL, hashed_given_url VARCHAR(40) DEFAULT NULL, language VARCHAR(20) DEFAULT NULL, is_not_parsed BOOLEAN DEFAULT 0, CONSTRAINT FK_F4D18282A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('entry') . ' (id, user_id, uid, title, url, hashed_url, origin_url, given_url, hashed_given_url, is_archived, archived_at, is_starred, content, created_at, updated_at, published_at, published_by, starred_at, mimetype, language, reading_time, domain_name, preview_picture, http_status, headers, is_not_parsed) SELECT id, user_id, uid, title, url, hashed_url, origin_url, given_url, hashed_given_url, is_archived, archived_at, is_starred, content, created_at, updated_at, published_at, published_by, starred_at, mimetype, language, reading_time, domain_name, preview_picture, http_status, headers, is_not_parsed FROM __temp__wallabag_entry'); + $this->addSql('DROP TABLE __temp__wallabag_entry'); + $this->addSql('CREATE INDEX IDX_F4D18282A76ED395 ON ' . $this->getTable('entry') . ' (user_id)'); + $this->addSql('CREATE INDEX user_starred ON ' . $this->getTable('entry') . ' (user_id, is_starred, starred_at)'); + $this->addSql('CREATE INDEX user_created ON ' . $this->getTable('entry') . ' (user_id, created_at)'); + $this->addSql('CREATE INDEX user_archived ON ' . $this->getTable('entry') . ' (user_id, is_archived, archived_at)'); + $this->addSql('CREATE INDEX user_language ON ' . $this->getTable('entry') . ' (language, user_id)'); + $this->addSql('CREATE INDEX hashed_given_url_user_id ON ' . $this->getTable('entry') . ' (user_id, hashed_given_url)'); + $this->addSql('CREATE INDEX hashed_url_user_id ON ' . $this->getTable('entry') . ' (user_id, hashed_url)'); + $this->addSql('CREATE INDEX created_at ON ' . $this->getTable('entry') . ' (created_at)'); + $this->addSql('CREATE INDEX uid ON ' . $this->getTable('entry') . ' (uid)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_entry_tag AS SELECT entry_id, tag_id FROM ' . $this->getTable('entry_tag')); + $this->addSql('DROP TABLE ' . $this->getTable('entry_tag')); + $this->addSql('CREATE TABLE ' . $this->getTable('entry_tag') . ' (entry_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY(entry_id, tag_id), CONSTRAINT FK_C9F0DD7CBA364942 FOREIGN KEY (entry_id) REFERENCES ' . $this->getTable('entry') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C9F0DD7CBAD26311 FOREIGN KEY (tag_id) REFERENCES ' . $this->getTable('tag') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('entry_tag') . ' (entry_id, tag_id) SELECT entry_id, tag_id FROM __temp__wallabag_entry_tag'); + $this->addSql('DROP TABLE __temp__wallabag_entry_tag'); + $this->addSql('CREATE INDEX IDX_C9F0DD7CBA364942 ON ' . $this->getTable('entry_tag') . ' (entry_id)'); + $this->addSql('CREATE INDEX IDX_C9F0DD7CBAD26311 ON ' . $this->getTable('entry_tag') . ' (tag_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_ignore_origin_user_rule AS SELECT id, config_id, rule FROM ' . $this->getTable('ignore_origin_user_rule')); + $this->addSql('DROP TABLE ' . $this->getTable('ignore_origin_user_rule')); + $this->addSql('CREATE TABLE ' . $this->getTable('ignore_origin_user_rule') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, config_id INTEGER NOT NULL, rule VARCHAR(255) NOT NULL, CONSTRAINT fk_config FOREIGN KEY (config_id) REFERENCES ' . $this->getTable('config') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('ignore_origin_user_rule') . ' (id, config_id, rule) SELECT id, config_id, rule FROM __temp__wallabag_ignore_origin_user_rule'); + $this->addSql('DROP TABLE __temp__wallabag_ignore_origin_user_rule'); + $this->addSql('CREATE INDEX idx_config ON ' . $this->getTable('ignore_origin_user_rule') . ' (config_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_internal_setting AS SELECT name, section, value FROM ' . $this->getTable('internal_setting')); + $this->addSql('DROP TABLE ' . $this->getTable('internal_setting')); + $this->addSql('CREATE TABLE ' . $this->getTable('internal_setting') . ' (name VARCHAR(255) NOT NULL, value VARCHAR(255) DEFAULT NULL, section VARCHAR(255) DEFAULT NULL, PRIMARY KEY(name))'); + $this->addSql('INSERT INTO ' . $this->getTable('internal_setting') . ' (name, section, value) SELECT name, section, value FROM __temp__wallabag_internal_setting'); + $this->addSql('DROP TABLE __temp__wallabag_internal_setting'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_5D9649505E237E06 ON ' . $this->getTable('internal_setting') . ' (name)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_access_tokens AS SELECT id, client_id, user_id, token, expires_at, scope FROM ' . $this->getTable('oauth2_access_tokens')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_access_tokens')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_access_tokens') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191), CONSTRAINT FK_368A420919EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_368A4209A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_access_tokens') . ' (id, client_id, user_id, token, expires_at, scope) SELECT id, client_id, user_id, token, expires_at, scope FROM __temp__wallabag_oauth2_access_tokens'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_access_tokens'); + $this->addSql('CREATE INDEX IDX_368A420919EB6921 ON ' . $this->getTable('oauth2_access_tokens') . ' (client_id)'); + $this->addSql('CREATE INDEX IDX_368A4209A76ED395 ON ' . $this->getTable('oauth2_access_tokens') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_auth_codes AS SELECT id, client_id, user_id, token, redirect_uri, expires_at, scope FROM ' . $this->getTable('oauth2_auth_codes')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_auth_codes')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_auth_codes') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, redirect_uri CLOB NOT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191), CONSTRAINT FK_EE52E3FA19EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EE52E3FAA76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_auth_codes') . ' (id, client_id, user_id, token, redirect_uri, expires_at, scope) SELECT id, client_id, user_id, token, redirect_uri, expires_at, scope FROM __temp__wallabag_oauth2_auth_codes'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_auth_codes'); + $this->addSql('CREATE INDEX IDX_EE52E3FA19EB6921 ON ' . $this->getTable('oauth2_auth_codes') . ' (client_id)'); + $this->addSql('CREATE INDEX IDX_EE52E3FAA76ED395 ON ' . $this->getTable('oauth2_auth_codes') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_clients AS SELECT id, user_id, random_id, redirect_uris, secret, allowed_grant_types, name FROM ' . $this->getTable('oauth2_clients')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_clients')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_clients') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, random_id VARCHAR(255) NOT NULL, secret VARCHAR(255) NOT NULL, name CLOB NOT NULL, redirect_uris CLOB NOT NULL, allowed_grant_types CLOB NOT NULL, CONSTRAINT FK_635D765EA76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_clients') . ' (id, user_id, random_id, redirect_uris, secret, allowed_grant_types, name) SELECT id, user_id, random_id, redirect_uris, secret, allowed_grant_types, name FROM __temp__wallabag_oauth2_clients'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_clients'); + $this->addSql('CREATE INDEX IDX_635D765EA76ED395 ON ' . $this->getTable('oauth2_clients') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_oauth2_refresh_tokens AS SELECT id, client_id, user_id, token, expires_at, scope FROM ' . $this->getTable('oauth2_refresh_tokens')); + $this->addSql('DROP TABLE ' . $this->getTable('oauth2_refresh_tokens')); + $this->addSql('CREATE TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191), CONSTRAINT FK_20C9FB2419EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_20C9FB24A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('oauth2_clients') . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('oauth2_refresh_tokens') . ' (id, client_id, user_id, token, expires_at, scope) SELECT id, client_id, user_id, token, expires_at, scope FROM __temp__wallabag_oauth2_refresh_tokens'); + $this->addSql('DROP TABLE __temp__wallabag_oauth2_refresh_tokens'); + $this->addSql('CREATE INDEX IDX_20C9FB2419EB6921 ON ' . $this->getTable('oauth2_refresh_tokens') . ' (client_id)'); + $this->addSql('CREATE INDEX IDX_20C9FB24A76ED395 ON ' . $this->getTable('oauth2_refresh_tokens') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_site_credential AS SELECT id, user_id, host, username, password, createdAt, updated_at FROM ' . $this->getTable('site_credential')); + $this->addSql('DROP TABLE ' . $this->getTable('site_credential')); + $this->addSql('CREATE TABLE ' . $this->getTable('site_credential') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER NOT NULL, host VARCHAR(255) NOT NULL, username CLOB NOT NULL, password CLOB NOT NULL, createdAt DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES ' . $this->getTable('user') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('site_credential') . ' (id, user_id, host, username, password, createdAt, updated_at) SELECT id, user_id, host, username, password, createdAt, updated_at FROM __temp__wallabag_site_credential'); + $this->addSql('DROP TABLE __temp__wallabag_site_credential'); + $this->addSql('CREATE INDEX idx_user ON ' . $this->getTable('site_credential') . ' (user_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_tag AS SELECT id, label, slug FROM ' . $this->getTable('tag')); + $this->addSql('DROP TABLE ' . $this->getTable('tag')); + $this->addSql('CREATE TABLE ' . $this->getTable('tag') . ' (id INTEGER PRIMARY KEY NOT NULL, label CLOB NOT NULL, slug VARCHAR(128) NOT NULL)'); + $this->addSql('INSERT INTO ' . $this->getTable('tag') . ' (id, label, slug) SELECT id, label, slug FROM __temp__wallabag_tag'); + $this->addSql('DROP TABLE __temp__wallabag_tag'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_4CA58A8C989D9B62 ON ' . $this->getTable('tag') . ' (slug)'); + $this->addSql('CREATE INDEX tag_label ON ' . $this->getTable('tag') . ' (label)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__wallabag_tagging_rule AS SELECT id, config_id, rule, tags FROM ' . $this->getTable('tagging_rule')); + $this->addSql('DROP TABLE ' . $this->getTable('tagging_rule')); + $this->addSql('CREATE TABLE ' . $this->getTable('tagging_rule') . ' (id INTEGER PRIMARY KEY NOT NULL, config_id INTEGER DEFAULT NULL, rule VARCHAR(255) NOT NULL, tags CLOB NOT NULL, CONSTRAINT FK_2D9B3C5424DB0683 FOREIGN KEY (config_id) REFERENCES ' . $this->getTable('config') . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $this->getTable('tagging_rule') . ' (id, config_id, rule, tags) SELECT id, config_id, rule, tags FROM __temp__wallabag_tagging_rule'); + $this->addSql('DROP TABLE __temp__wallabag_tagging_rule'); + $this->addSql('CREATE INDEX IDX_2D9B3C5424DB0683 ON ' . $this->getTable('tagging_rule') . ' (config_id)'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor FROM ' . $this->getTable('user')); + $this->addSql('DROP TABLE ' . $this->getTable('user')); + $this->addSql('CREATE TABLE ' . $this->getTable('user') . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, username_canonical VARCHAR(180) NOT NULL, email VARCHAR(180) NOT NULL, email_canonical VARCHAR(180) NOT NULL, enabled BOOLEAN NOT NULL, salt VARCHAR(255) DEFAULT NULL, password VARCHAR(255) NOT NULL, last_login DATETIME DEFAULT NULL, confirmation_token VARCHAR(180) DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:array) + , name CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, googleAuthenticatorSecret VARCHAR(255) DEFAULT NULL, backupCodes CLOB DEFAULT NULL --(DC2Type:json) + , emailTwoFactor BOOLEAN NOT NULL)'); + $this->addSql('INSERT INTO ' . $this->getTable('user') . ' (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, googleAuthenticatorSecret, backupCodes, emailTwoFactor FROM __temp__user'); + $this->addSql('DROP TABLE __temp__user'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5C05FB297 ON ' . $this->getTable('user') . ' (confirmation_token)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5A0D96FBF ON ' . $this->getTable('user') . ' (email_canonical)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E592FC23A8 ON ' . $this->getTable('user') . ' (username_canonical)'); + } + } + + private function renameForeignKey(string $tableName, string $oldName, string $foreignTableName, string $foreignColumnName, bool $onDeleteCascade = false): void + { + $newName = $this->getForeignKeyName($tableName, $foreignColumnName); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' DROP FOREIGN KEY ' . $oldName . ';'); + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' ADD CONSTRAINT ' . $newName . ' FOREIGN KEY (' . $foreignColumnName . ') REFERENCES ' . $this->getTable($foreignTableName) . ' (id)' . ($onDeleteCascade ? ' ON DELETE CASCADE' : '') . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME CONSTRAINT ' . $oldName . ' TO ' . $newName . ';'); + break; + } + } + + private function revertRenameForeignKey(string $tableName, string $oldName, string $foreignTableName, string $foreignColumnName, bool $onDeleteCascade = false): void + { + $newName = $this->getForeignKeyName($tableName, $foreignColumnName); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' DROP FOREIGN KEY ' . $newName . ';'); + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' ADD CONSTRAINT ' . $oldName . ' FOREIGN KEY (' . $foreignColumnName . ') REFERENCES ' . $this->getTable($foreignTableName) . ' (id)' . ($onDeleteCascade ? ' ON DELETE CASCADE' : '') . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME CONSTRAINT ' . $newName . ' TO ' . $oldName . ';'); + break; + } + } + + private function renameIndex(string $tableName, string $oldName, $indexedColumnNames): void + { + $indexedColumnNames = (array) $indexedColumnNames; + + $newName = $this->getIndexName($tableName, $indexedColumnNames); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME INDEX ' . $oldName . ' TO ' . $newName . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('ALTER INDEX ' . $oldName . ' RENAME TO ' . $newName . ';'); + break; + } + } + + private function revertRenameIndex(string $tableName, string $oldName, $indexedColumnNames): void + { + $indexedColumnNames = (array) $indexedColumnNames; + + $newName = $this->getIndexName($tableName, $indexedColumnNames); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME INDEX ' . $newName . ' TO ' . $oldName . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $oldName = strtolower($oldName); + $this->addSql('ALTER INDEX ' . $newName . ' RENAME TO ' . $oldName . ';'); + break; + } + } + + private function dropIndex(string $tableName, string $indexedColumnName, ?string $name = null): void + { + if (null === $name) { + $name = $this->getIndexName($tableName, [$indexedColumnName]); + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('DROP INDEX ' . $name . ' ON ' . $this->getTable($tableName) . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('DROP INDEX ' . $name . ';'); + break; + } + } + + private function createIndex(string $tableName, string $indexedColumnName, ?string $name = null): void + { + if (null === $name) { + $name = $this->getIndexName($tableName, [$indexedColumnName]); + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + case $platform instanceof PostgreSQLPlatform: + $this->addSql('CREATE INDEX ' . $name . ' ON ' . $this->getTable($tableName) . ' (' . $indexedColumnName . ');'); + break; + } + } + + private function renameUniqueIndex(string $tableName, string $oldName, string $indexedColumnName): void + { + $platform = $this->connection->getDatabasePlatform(); + + $newName = $this->getUniqueIndexName($tableName, $indexedColumnName); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME INDEX ' . $oldName . ' TO ' . $newName . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('ALTER INDEX ' . $oldName . ' RENAME TO ' . $newName . ';'); + break; + } + } + + private function revertRenameUniqueIndex(string $tableName, string $oldName, string $indexedColumnName): void + { + $platform = $this->connection->getDatabasePlatform(); + + $newName = $this->getUniqueIndexName($tableName, $indexedColumnName); + + if (strtolower($oldName) === strtolower($newName)) { + return; + } + + switch (true) { + case $platform instanceof MySQLPlatform: + $this->addSql('ALTER TABLE ' . $this->getTable($tableName) . ' RENAME INDEX ' . $newName . ' TO ' . $oldName . ';'); + break; + case $platform instanceof PostgreSQLPlatform: + $this->addSql('ALTER INDEX ' . $newName . ' RENAME TO ' . $oldName . ';'); + break; + } + } + + private function dropUniqueIndex(string $tableName, string $indexedColumnName, ?string $name = null): void + { + if (null === $name) { + $name = $this->getUniqueIndexName($tableName, $indexedColumnName); + } + + $this->dropIndex($tableName, $indexedColumnName, $name); + } + + private function createUniqueIndex(string $tableName, string $indexedColumnName, ?string $name = null): void + { + if (null === $name) { + $name = $this->getUniqueIndexName($tableName, $indexedColumnName); + } + + $platform = $this->connection->getDatabasePlatform(); + + switch (true) { + case $platform instanceof MySQLPlatform: + case $platform instanceof PostgreSQLPlatform: + $this->addSql('CREATE UNIQUE INDEX ' . $name . ' ON ' . $this->getTable($tableName) . ' (' . $indexedColumnName . ');'); + break; + } + } +} diff --git a/src/Doctrine/WallabagMigration.php b/src/Doctrine/WallabagMigration.php index 84e0846ff..4679b9852 100644 --- a/src/Doctrine/WallabagMigration.php +++ b/src/Doctrine/WallabagMigration.php @@ -41,6 +41,11 @@ abstract class WallabagMigration extends AbstractMigration implements ContainerA return false; } + protected function getTablePrefix(): string + { + return (string) $this->container->getParameter('database_table_prefix'); + } + protected function getTable($tableName, $unEscaped = false) { $table = $this->container->getParameter('database_table_prefix') . $tableName; @@ -57,4 +62,35 @@ abstract class WallabagMigration extends AbstractMigration implements ContainerA // return escaped table return '`' . $table . '`'; } + + protected function getForeignKeyName(string $tableName, string $foreignColumnName): string + { + return $this->generateIdentifierName([$this->getTable($tableName, true), $foreignColumnName], 'fk'); + } + + protected function getIndexName(string $tableName, $indexedColumnNames): string + { + $indexedColumnNames = (array) $indexedColumnNames; + + return $this->generateIdentifierName(array_merge([$this->getTable($tableName, true)], $indexedColumnNames), 'idx'); + } + + protected function getUniqueIndexName(string $tableName, string $indexedColumnName): string + { + return $this->generateIdentifierName([$this->getTable($tableName, true), $indexedColumnName], 'uniq'); + } + + /** + * @see \Doctrine\DBAL\Schema\AbstractAsset::_generateIdentifierName + * + * @param string[] $columnNames + */ + protected function generateIdentifierName(array $columnNames, string $prefix = ''): string + { + $hash = implode('', array_map(static function ($column): string { + return dechex(crc32($column)); + }, $columnNames)); + + return strtoupper(substr($prefix . '_' . $hash, 0, $this->platform->getMaxIdentifierLength())); + } } diff --git a/src/Entity/Api/AccessToken.php b/src/Entity/Api/AccessToken.php index 39a0c2bc9..e332207a1 100644 --- a/src/Entity/Api/AccessToken.php +++ b/src/Entity/Api/AccessToken.php @@ -8,22 +8,6 @@ use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken; /** * @ORM\Table("oauth2_access_tokens") * @ORM\Entity - * @ORM\AttributeOverrides({ - * @ORM\AttributeOverride(name="token", - * column=@ORM\Column( - * name = "token", - * type = "string", - * length = 191 - * ) - * ), - * @ORM\AttributeOverride(name="scope", - * column=@ORM\Column( - * name = "scope", - * type = "string", - * length = 191 - * ) - * ) - * }) */ class AccessToken extends BaseAccessToken { diff --git a/src/Entity/Api/AuthCode.php b/src/Entity/Api/AuthCode.php index 530862d29..85c61c5e2 100644 --- a/src/Entity/Api/AuthCode.php +++ b/src/Entity/Api/AuthCode.php @@ -8,22 +8,6 @@ use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode; /** * @ORM\Table("oauth2_auth_codes") * @ORM\Entity - * @ORM\AttributeOverrides({ - * @ORM\AttributeOverride(name="token", - * column=@ORM\Column( - * name = "token", - * type = "string", - * length = 191 - * ) - * ), - * @ORM\AttributeOverride(name="scope", - * column=@ORM\Column( - * name = "scope", - * type = "string", - * length = 191 - * ) - * ) - * }) */ class AuthCode extends BaseAuthCode { diff --git a/src/Entity/Api/RefreshToken.php b/src/Entity/Api/RefreshToken.php index 957c9eb0c..9c6e9b9b8 100644 --- a/src/Entity/Api/RefreshToken.php +++ b/src/Entity/Api/RefreshToken.php @@ -8,22 +8,6 @@ use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken; /** * @ORM\Table("oauth2_refresh_tokens") * @ORM\Entity - * @ORM\AttributeOverrides({ - * @ORM\AttributeOverride(name="token", - * column=@ORM\Column( - * name = "token", - * type = "string", - * length = 191 - * ) - * ), - * @ORM\AttributeOverride(name="scope", - * column=@ORM\Column( - * name = "scope", - * type = "string", - * length = 191 - * ) - * ) - * }) */ class RefreshToken extends BaseRefreshToken { diff --git a/src/Entity/Config.php b/src/Entity/Config.php index a08948adf..12ba65eec 100644 --- a/src/Entity/Config.php +++ b/src/Entity/Config.php @@ -14,7 +14,7 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Table( * name="`config`", * indexes={ - * @ORM\Index(name="config_feed_token", columns={"feed_token"}, options={"lengths"={255}}), + * @ORM\Index(columns={"feed_token"}), * } * ) */ diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php index 71722b61c..6f109bac2 100644 --- a/src/Entity/Entry.php +++ b/src/Entity/Entry.php @@ -21,16 +21,15 @@ use Wallabag\Helper\UrlHasher; * @ORM\Entity(repositoryClass="Wallabag\Repository\EntryRepository") * @ORM\Table( * name="`entry`", - * options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"}, * indexes={ - * @ORM\Index(name="created_at", columns={"created_at"}), - * @ORM\Index(name="uid", columns={"uid"}), - * @ORM\Index(name="hashed_url_user_id", columns={"user_id", "hashed_url"}, options={"lengths"={null, 40}}), - * @ORM\Index(name="hashed_given_url_user_id", columns={"user_id", "hashed_given_url"}, options={"lengths"={null, 40}}), - * @ORM\Index(name="user_language", columns={"language", "user_id"}), - * @ORM\Index(name="user_archived", columns={"user_id", "is_archived", "archived_at"}), - * @ORM\Index(name="user_created", columns={"user_id", "created_at"}), - * @ORM\Index(name="user_starred", columns={"user_id", "is_starred", "starred_at"}) + * @ORM\Index(columns={"created_at"}), + * @ORM\Index(columns={"uid"}), + * @ORM\Index(columns={"user_id", "hashed_url"}), + * @ORM\Index(columns={"user_id", "hashed_given_url"}), + * @ORM\Index(columns={"language", "user_id"}), + * @ORM\Index(columns={"user_id", "is_archived", "archived_at"}), + * @ORM\Index(columns={"user_id", "created_at"}), + * @ORM\Index(columns={"user_id", "is_starred", "starred_at"}) * } * ) * @ORM\HasLifecycleCallbacks() @@ -279,7 +278,7 @@ class Entry * * @Exclude * - * @ORM\Column(name="is_not_parsed", type="boolean") + * @ORM\Column(name="is_not_parsed", type="boolean", options={"default": false}) * * @Groups({"entries_for_user", "export_all"}) */ @@ -1017,11 +1016,9 @@ class Entry /** * Set isNotParsed. * - * @param bool $isNotParsed - * * @return Entry */ - public function setNotParsed($isNotParsed) + public function setNotParsed(bool $isNotParsed) { $this->isNotParsed = $isNotParsed; @@ -1030,10 +1027,8 @@ class Entry /** * Get isNotParsed. - * - * @return bool */ - public function isNotParsed() + public function isNotParsed(): bool { return $this->isNotParsed; } diff --git a/src/Entity/IgnoreOriginUserRule.php b/src/Entity/IgnoreOriginUserRule.php index 09f6359ea..7b41182d2 100644 --- a/src/Entity/IgnoreOriginUserRule.php +++ b/src/Entity/IgnoreOriginUserRule.php @@ -38,6 +38,7 @@ class IgnoreOriginUserRule implements IgnoreOriginRuleInterface, RuleInterface /** * @ORM\ManyToOne(targetEntity="Wallabag\Entity\Config", inversedBy="ignoreOriginRules") + * @ORM\JoinColumn(nullable=false) */ private $config; diff --git a/src/Entity/InternalSetting.php b/src/Entity/InternalSetting.php index 337492a8b..705fa7121 100644 --- a/src/Entity/InternalSetting.php +++ b/src/Entity/InternalSetting.php @@ -12,25 +12,13 @@ use Doctrine\ORM\Mapping as ORM; * * @ORM\Entity(repositoryClass="Craue\ConfigBundle\Repository\SettingRepository") * @ORM\Table(name="`internal_setting`") - * @ORM\AttributeOverrides({ - * @ORM\AttributeOverride(name="name", - * column=@ORM\Column( - * length = 191 - * ) - * ), - * @ORM\AttributeOverride(name="section", - * column=@ORM\Column( - * length = 191 - * ) - * ) - * }) */ class InternalSetting extends BaseSetting { /** * @var string|null * - * @ORM\Column(name="value", type="string", nullable=true, length=191) + * @ORM\Column(name="value", type="string", nullable=true) */ protected $value; } diff --git a/src/Entity/SiteCredential.php b/src/Entity/SiteCredential.php index c8fba0729..5c8da7672 100644 --- a/src/Entity/SiteCredential.php +++ b/src/Entity/SiteCredential.php @@ -59,14 +59,15 @@ class SiteCredential private $createdAt; /** - * @var \DateTime + * @var \DateTime|null * - * @ORM\Column(name="updated_at", type="datetime") + * @ORM\Column(name="updated_at", type="datetime", nullable=true) */ private $updatedAt; /** * @ORM\ManyToOne(targetEntity="Wallabag\Entity\User", inversedBy="siteCredentials") + * @ORM\JoinColumn(nullable=false) */ private $user; @@ -187,7 +188,7 @@ class SiteCredential /** * Get updatedAt. * - * @return \DateTime + * @return \DateTime|null */ public function getUpdatedAt() { diff --git a/src/Entity/Tag.php b/src/Entity/Tag.php index f92aa9168..ef8233eb2 100644 --- a/src/Entity/Tag.php +++ b/src/Entity/Tag.php @@ -15,9 +15,8 @@ use JMS\Serializer\Annotation\XmlRoot; * @XmlRoot("tag") * @ORM\Table( * name="`tag`", - * options={"collate"="utf8mb4_bin", "charset"="utf8mb4"}, * indexes={ - * @ORM\Index(name="tag_label", columns={"label"}, options={"lengths"={255}}), + * @ORM\Index(columns={"label"}), * } * ) * @ORM\Entity(repositoryClass="Wallabag\Repository\TagRepository") diff --git a/src/Event/Subscriber/SchemaAdapterSubscriber.php b/src/Event/Subscriber/SchemaAdapterSubscriber.php new file mode 100644 index 000000000..018877e97 --- /dev/null +++ b/src/Event/Subscriber/SchemaAdapterSubscriber.php @@ -0,0 +1,68 @@ +databaseTablePrefix = $databaseTablePrefix; + } + + public function getSubscribedEvents() + { + return ['postGenerateSchema']; + } + + public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs) + { + $platform = $eventArgs->getEntityManager()->getConnection()->getDatabasePlatform(); + + if (!$platform instanceof MySQLPlatform) { + return; + } + + $schema = $eventArgs->getSchema(); + + $entryTable = $schema->getTable($this->databaseTablePrefix . 'entry'); + $entryTable->addOption('collate', 'utf8mb4_unicode_ci'); + $entryTable->addOption('charset', 'utf8mb4'); + + $tagTable = $schema->getTable($this->databaseTablePrefix . 'tag'); + $tagTable->addOption('collate', 'utf8mb4_bin'); + $tagTable->addOption('charset', 'utf8mb4'); + + foreach ($tagTable->getIndexes() as $index) { + if ($index->getColumns() === ['label']) { + $tagTable->dropIndex($index->getName()); + $tagTable->addIndex($index->getColumns(), $index->getName(), $index->getFlags(), array_merge( + $index->getOptions(), + ['lengths' => [255]] + )); + } + } + + $oauth2AccessTokenTable = $schema->getTable($this->databaseTablePrefix . 'oauth2_access_tokens'); + $oauth2AccessTokenTable->modifyColumn('token', ['length' => 191]); + $oauth2AccessTokenTable->modifyColumn('scope', ['length' => 191]); + + $oauth2AuthCodeTable = $schema->getTable($this->databaseTablePrefix . 'oauth2_auth_codes'); + $oauth2AuthCodeTable->modifyColumn('token', ['length' => 191]); + $oauth2AuthCodeTable->modifyColumn('scope', ['length' => 191]); + + $oauth2RefreshTokenTable = $schema->getTable($this->databaseTablePrefix . 'oauth2_refresh_tokens'); + $oauth2RefreshTokenTable->modifyColumn('token', ['length' => 191]); + $oauth2RefreshTokenTable->modifyColumn('scope', ['length' => 191]); + + $internalSettingTable = $schema->getTable($this->databaseTablePrefix . 'internal_setting'); + $internalSettingTable->modifyColumn('name', ['length' => 191]); + $internalSettingTable->modifyColumn('section', ['length' => 191]); + $internalSettingTable->modifyColumn('value', ['length' => 191]); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f9e89ba44..fad77b3bb 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -40,6 +40,17 @@ if (!isPartialRun()) { ]))->mustRun(function ($type, $buffer) { echo $buffer; }); + + (new Process([ + 'php', + __DIR__ . '/../bin/console', + 'doctrine:schema:validate', + '--no-interaction', + '--env=test', + '-v', + ]))->mustRun(function ($type, $buffer) { + echo $buffer; + }); } (new Process([