connection = $connection; $this->logger = $logger; $tempBaseDir = $tempManager->getTempBaseDir(); $this->backupPath = tempnam($tempBaseDir, 'mail_recipients_backup'); } /** * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` * @param array $options */ #[\Override] public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { // Keep recipients backup $qb1 = $this->connection->getQueryBuilder(); $qb1->select('local_message_id', 'message_id', 'type', 'label', 'email') ->from('mail_recipients') ->where($qb1->expr()->isNotNull('local_message_id')); $result = $qb1->executeQuery(); $this->recipients = $result->fetchAll(); $result->closeCursor(); $backupFile = fopen($this->backupPath, 'rb+'); if (is_resource($backupFile)) { $this->logger->warning( 'Migration Version1130Date20220412111833 is going to truncate the mail_recipients table. ' . 'We made a backup of the outbox recipients and try to restore them later. When the migration fails restore the data manually. ' . 'Path to backup file: ' . $this->backupPath ); fputcsv($backupFile, ['local_message_id', 'message_id', 'type', 'label', 'email']); foreach ($this->recipients as $recipient) { fputcsv($backupFile, array_values($recipient)); } fclose($backupFile); } // Truncate recipients table $sql1 = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*mail_recipients`', false); $this->connection->executeStatement($sql1); // Truncate and change primary key type for messages table $sql2 = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*mail_messages`', false); $this->connection->executeStatement($sql2); // Truncate message_tags table $sql3 = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*mail_message_tags`', false); $this->connection->executeStatement($sql3); // unset all locks for the mailboxes table $qb2 = $this->connection->getQueryBuilder(); $qb2->update('mail_mailboxes') ->set('sync_new_lock', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)) ->set('sync_changed_lock', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)) ->set('sync_vanished_lock', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)) ->set('sync_new_token', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)) ->set('sync_changed_token', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)) ->set('sync_vanished_token', $qb2->createNamedParameter(null, IQueryBuilder::PARAM_NULL)); $qb2->executeStatement(); } /** * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` * @param array $options * @return null|ISchemaWrapper */ #[\Override] public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); // bigint and primary key with autoincrement is not possible on sqlite: https://github.com/f7cloud/server/commit/f57e334f8395e3b5c046b6d28480d798453e4866 $isSqlite = $this->connection->getDatabasePlatform() instanceof SqlitePlatform; // Remove old unnamed attachments FK $attachmentsTable = $schema->getTable('mail_attachments'); $fks = $attachmentsTable->getForeignKeys(); foreach ($fks as $fk) { $attachmentsTable->removeForeignKey($fk->getName()); } $recipientsTable = $schema->getTable('mail_recipients'); $fks = $recipientsTable->getForeignKeys(); foreach ($fks as $fk) { $recipientsTable->removeForeignKey($fk->getName()); } if (!$isSqlite) { // Change primary column to bigint $recipientsTable->modifyColumn('id', [ 'type' => Type::getType(Types::BIGINT), ]); } // Add new named FKs to attachments and recipients $recipientsTable->addForeignKeyConstraint($schema->getTable('mail_local_messages'), ['local_message_id'], ['id'], ['onDelete' => 'CASCADE'], 'recipient_local_message'); $attachmentsTable->addForeignKeyConstraint($schema->getTable('mail_local_messages'), ['local_message_id'], ['id'], ['onDelete' => 'CASCADE'], 'attachment_local_message'); $messagesTable = $schema->getTable('mail_messages'); if (!$isSqlite) { // Change primary column to bigint $messagesTable->modifyColumn('id', [ 'type' => Type::getType(Types::BIGINT), ]); } // Since we have instances where these indices were set manually, remove them first if they exist $indices = $messagesTable->getIndexes(); foreach ($indices as $index) { if (!$index->isPrimary()) { $messagesTable->dropIndex($index->getName()); } } // Add named indices $messagesTable->addIndex(['mailbox_id', 'flag_important', 'flag_deleted', 'flag_seen'], 'mail_messages_id_flags'); $messagesTable->addIndex(['mailbox_id', 'flag_deleted', 'flag_flagged'], 'mail_messages_id_flags2'); // Dropped in Version3600Date20240205180726 because mail_messages_mailbox_id is redundant with mail_messages_mb_id_uid // $messagesTable->addIndex(['mailbox_id'], 'mail_messages_mailbox_id'); // mail_messages_msgid_idx was added later and may not exist until optional indices are created $messagesTable->addIndex( ['message_id'], 'mail_messages_msgid_idx', [], ['lengths' => [128]], ); // Postgres doesn't allow for shortened indices, so let's skip the last index. if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) { return $schema; } $messagesTable->addIndex(['mailbox_id', 'thread_root_id', 'sent_at'], 'mail_msg_thrd_root_snt_idx', [], ['lengths' => [null, 64, null]]); return $schema; } /** * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` * @param array $options * * @return void */ #[\Override] public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) { $qb1 = $this->connection->getQueryBuilder(); $qb1->insert('mail_recipients') ->values([ 'local_message_id' => $qb1->createParameter('local_message_id'), 'message_id' => $qb1->createParameter('message_id'), 'type' => $qb1->createParameter('type'), 'label' => $qb1->createParameter('label'), 'email' => $qb1->createParameter('email'), ]); foreach ($this->recipients as $recipient) { $qb1->setParameters($recipient); $qb1->executeStatement(); } if (is_file($this->backupPath)) { $this->logger->warning('Migration Version1130Date20220412111833 completed. Going to delete backup file: ' . $this->backupPath); @unlink($this->backupPath); } } }