Skip to content

Commit

Permalink
Migration commands for MongoDB thread and message metadata
Browse files Browse the repository at this point in the history
These commands are intended to be run once to populate metadata fields from the old hash properties on thread and message documents. The commands will not unset the original hash properties -- that is left to the user.
  • Loading branch information
jmikola committed Dec 15, 2011
1 parent cef0798 commit 3c41805
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
129 changes: 129 additions & 0 deletions Command/MongoDBMigrateMessageMetadataCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Ornicar\MessageBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class MongoDBMigrateMessageMetadataCommand extends ContainerAwareCommand
{
/**
* @var \MongoCollection
*/
private $messageCollection;

/**
* @var \MongoCollection
*/
private $participantCollection;

/**
* @see Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this
->setName('ornicar:message:mongodb:migrate:message-metadata')
->setDescription('Migrates message document hash fields to embedded metadata')
->addArgument('participantClass', InputArgument::REQUIRED, 'Participant class')
->addOption('safe', null, InputOption::VALUE_OPTIONAL, 'Mongo update option', false)
->addOption('fsync', null, InputOption::VALUE_OPTIONAL, 'Mongo update option', false)
;
}

/**
* @see Symfony\Bundle\FrameworkBundle\Command\Command::initialize()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);

$registry = $this->getContainer()->get('doctrine.odm.mongodb');
$messageClass = $this->getContainer()->getParameter('ornicar_message.message_class');
$participantClass = $input->getArgument('participantClass');

if (!$dm = $registry->getManagerForClass($messageClass)) {
throw new \RuntimeException(sprintf('There is no DocumentManager for message class "%s"', $messageClass));
}

$this->messageCollection = $registry
->getManagerForClass($messageClass)
->getDocumentCollection($messageClass)
->getMongoCollection();
$this->participantCollection = $registry
->getManagerForClass($participantClass)
->getDocumentCollection($participantClass)
->getMongoCollection();
}

/**
* @see Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$cursor = $this->messageCollection->find(
array('metadata' => array('$exists' => false)),
array('isReadByParticipant' => 1)
);

$updateOptions = array(
'multiple' => false,
'safe' => $input->getOption('safe'),
'fsync' => $input->getOption('fsync'),
);

$numProcessed = 0;

if (!$numTotal = $cursor->count()) {
$output->writeln('There are no message documents to migrate.');
return;
}

$printStatus = function() use ($output, &$numProcessed, $numTotal) {
$output->write(sprintf("Processed: <info>%d</info> / Complete: <info>%d%%</info>\r", $numProcessed, round(100 * ($numProcessed / $numTotal))));
};

declare(ticks=100) {
register_tick_function($printStatus);

foreach ($cursor as $message) {
$this->messageCollection->update(
array('_id' => $message['_id']),
array('$set' => array('metadata' => $this->createMessageMetadata($message))),
$updateOptions
);
++$numProcessed;
}
}

$printStatus();
echo \PHP_EOL;
$output->writeln(sprintf('Migrated <info>%d</info> message documents.', $numProcessed));
}

/**
* Create message metadata array
*
* By default, Mongo will not include "$db" when creating the participant
* reference. We'll add that manually to be consistent with Doctrine.
*
* @param array $message
* @return array
*/
private function createMessageMetadata(array $message)
{
$metadata = array();

foreach ($message['isReadByParticipant'] as $participantId => $isRead) {
$metadata[] = array(
'isRead' => $isRead,
'participant' => $this->participantCollection->createDBRef(array('_id' => new \MongoId($participantId))) + array('$db' => (string) $this->participantCollection->db),
);
}

return $metadata;
}
}
149 changes: 149 additions & 0 deletions Command/MongoDBMigrateThreadMetadataCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

namespace Ornicar\MessageBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class MongoDBMigrateThreadMetadataCommand extends ContainerAwareCommand
{
/**
* @var \MongoCollection
*/
private $threadCollection;

/**
* @var \MongoCollection
*/
private $participantCollection;

/**
* @see Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this
->setName('ornicar:message:mongodb:migrate:thread-metadata')
->setDescription('Migrates thread document hash fields to embedded metadata')
->addArgument('participantClass', InputArgument::REQUIRED, 'Participant class')
->addOption('safe', null, InputOption::VALUE_OPTIONAL, 'Mongo update option', false)
->addOption('fsync', null, InputOption::VALUE_OPTIONAL, 'Mongo update option', false)
;
}

/**
* @see Symfony\Bundle\FrameworkBundle\Command\Command::initialize()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);

$registry = $this->getContainer()->get('doctrine.odm.mongodb');
$threadClass = $this->getContainer()->getParameter('ornicar_message.thread_class');
$participantClass = $input->getArgument('participantClass');

if (!$dm = $registry->getManagerForClass($threadClass)) {
throw new \RuntimeException(sprintf('There is no DocumentManager for thread class "%s"', $threadClass));
}

$this->threadCollection = $registry
->getManagerForClass($threadClass)
->getDocumentCollection($threadClass)
->getMongoCollection();
$this->participantCollection = $registry
->getManagerForClass($participantClass)
->getDocumentCollection($participantClass)
->getMongoCollection();
}

/**
* @see Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$cursor = $this->threadCollection->find(
array('metadata' => array('$exists' => false)),
array(
'datesOfLastMessageWrittenByOtherParticipant' => 1,
'datesOfLastMessageWrittenByParticipant' => 1,
'isDeletedByParticipant' => 1,
)
);

$updateOptions = array(
'multiple' => false,
'safe' => $input->getOption('safe'),
'fsync' => $input->getOption('fsync'),
);

$numProcessed = 0;

if (!$numTotal = $cursor->count()) {
$output->writeln('There are no thread documents to migrate.');
return;
}

$printStatus = function() use ($output, &$numProcessed, $numTotal) {
$output->write(sprintf("Processed: <info>%d</info> / Complete: <info>%d%%</info>\r", $numProcessed, round(100 * ($numProcessed / $numTotal))));
};

declare(ticks=100) {
register_tick_function($printStatus);

foreach ($cursor as $thread) {
$this->threadCollection->update(
array('_id' => $thread['_id']),
array('$set' => array('metadata' => $this->createThreadMetadata($thread))),
$updateOptions
);
++$numProcessed;
}
}

$printStatus();
echo \PHP_EOL;
$output->writeln(sprintf('Migrated <info>%d</info> thread documents.', $numProcessed));
}

/**
* Create thread metadata array
*
* By default, Mongo will not include "$db" when creating the participant
* reference. We'll add that manually to be consistent with Doctrine.
*
* @param array $thread
* @return array
*/
private function createThreadMetadata(array $thread)
{
$metadata = array();

$participantIds = array_keys($thread['datesOfLastMessageWrittenByOtherParticipant'] + $thread['datesOfLastMessageWrittenByParticipant'] + $thread['isDeletedByParticipant']);

foreach ($participantIds as $participantId) {
$meta = array(
'isDeleted' => false,
'participant' => $this->participantCollection->createDBRef(array('_id' => new \MongoId($participantId))) + array('$db' => (string) $this->participantCollection->db),
);

if (isset($thread['isDeletedByParticipant'][$participantId])) {
$meta['isDeleted'] = $thread['isDeletedByParticipant'][$participantId];
}

if (isset($thread['datesOfLastMessageWrittenByOtherParticipant'][$participantId])) {
$meta['lastMessageDate'] = new \MongoDate($thread['datesOfLastMessageWrittenByOtherParticipant'][$participantId]);
}

if (isset($thread['datesOfLastMessageWrittenByParticipant'][$participantId])) {
$meta['lastParticipantMessageDate'] = new \MongoDate($thread['datesOfLastMessageWrittenByParticipant'][$participantId]);
}

$metadata[] = $meta;
}

return $metadata;
}
}

0 comments on commit 3c41805

Please sign in to comment.