diff --git a/doctrine-mongo-mapping.xsd b/doctrine-mongo-mapping.xsd
index b5d4119b8c..76869f83fd 100644
--- a/doctrine-mongo-mapping.xsd
+++ b/doctrine-mongo-mapping.xsd
@@ -43,6 +43,7 @@
+
@@ -60,7 +61,7 @@
-
+
@@ -82,7 +83,7 @@
-
+
@@ -275,6 +276,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
index 8f2fe92f86..61d4a24945 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
@@ -80,3 +80,4 @@
require_once __DIR__ . '/PostLoad.php';
require_once __DIR__ . '/PreFlush.php';
require_once __DIR__ . '/HasLifecycleCallbacks.php';
+require_once __DIR__ . '/ShardKey.php';
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
index cf536475cc..da5929010d 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
@@ -27,5 +27,6 @@ final class Document extends AbstractDocument
public $repositoryClass;
public $indexes = array();
public $requireIndexes = false;
+ public $shardKey;
public $slaveOkay;
}
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php
new file mode 100644
index 0000000000..f707c975b3
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php
@@ -0,0 +1,30 @@
+.
+ */
+
+namespace Doctrine\ODM\MongoDB\Mapping\Annotations;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/** @Annotation */
+final class ShardKey extends Annotation
+{
+ public $keys = array();
+ public $unique;
+ public $numInitialChunks;
+}
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
index a850328089..6aa9826da5 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
@@ -20,7 +20,6 @@
namespace Doctrine\ODM\MongoDB\Mapping;
use Doctrine\Instantiator\Instantiator;
-use Doctrine\ODM\MongoDB\LockException;
/**
* A ClassMetadata instance holds all the object-document mapping metadata
@@ -115,6 +114,7 @@ public function __sleep()
'generatorOptions',
'idGenerator',
'indexes',
+ 'shardKey',
);
// The rest of the metadata is only serialized if necessary.
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
index bd80ac4293..e3f570bdcd 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
@@ -138,6 +138,7 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
$this->addInheritedFields($class, $parent);
$this->addInheritedRelations($class, $parent);
$this->addInheritedIndexes($class, $parent);
+ $this->setInheritedShardKey($class, $parent);
$class->setIdentifier($parent->identifier);
$class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField);
@@ -336,4 +337,20 @@ private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $par
$subClass->addIndex($index['keys'], $index['options']);
}
}
+
+ /**
+ * Adds inherited shard key to the subclass mapping.
+ *
+ * @param ClassMetadata $subClass
+ * @param ClassMetadata $parentClass
+ */
+ private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass)
+ {
+ if ($parentClass->isSharded()) {
+ $subClass->setShardKey(
+ $parentClass->shardKey['keys'],
+ $parentClass->shardKey['options']
+ );
+ }
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
index 7aad04e283..8ba727e31a 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
@@ -19,11 +19,10 @@
namespace Doctrine\ODM\MongoDB\Mapping;
-use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use Doctrine\ODM\MongoDB\LockException;
-use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Proxy\Proxy;
use Doctrine\ODM\MongoDB\Types\Type;
+use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use InvalidArgumentException;
/**
@@ -194,6 +193,11 @@ class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMet
*/
public $indexes = array();
+ /**
+ * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
+ */
+ public $shardKey;
+
/**
* READ-ONLY: Whether or not queries on this document should require indexes.
*/
@@ -518,7 +522,7 @@ public function setCustomRepositoryClass($repositoryClassName)
if ($this->isEmbeddedDocument) {
return;
}
-
+
if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
$repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
}
@@ -798,6 +802,67 @@ public function hasIndexes()
return $this->indexes ? true : false;
}
+ /**
+ * Set shard key for this Document.
+ *
+ * @param array $keys Array of document keys.
+ * @param array $options Array of sharding options.
+ *
+ * @throws MappingException
+ */
+ public function setShardKey(array $keys, array $options = array())
+ {
+ if ($this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
+ throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
+ }
+
+ if ($this->isEmbeddedDocument) {
+ throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
+ }
+
+ foreach ($keys as $field) {
+ if ($this->getTypeOfField($field) == 'increment') {
+ throw MappingException::noIncrementFieldsAllowedInShardKey($this->getName());
+ }
+ }
+
+ $this->shardKey = array(
+ 'keys' => array_map(function($value) {
+ if ($value == 1 || $value == -1) {
+ return (int) $value;
+ }
+ if (is_string($value)) {
+ $lower = strtolower($value);
+ if ($lower === 'asc') {
+ return 1;
+ } elseif ($lower === 'desc') {
+ return -1;
+ }
+ }
+ return $value;
+ }, $keys),
+ 'options' => $options
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function getShardKey()
+ {
+ return $this->shardKey;
+ }
+
+ /**
+ * Checks whether this document has shard key or not.
+ *
+ * @return bool
+ */
+ public function isSharded()
+ {
+ return $this->shardKey ? true : false;
+ }
+
/**
* Sets the change tracking policy used by this class.
*
@@ -1112,7 +1177,7 @@ public function mapField(array $mapping)
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
$mapping['isCascadeMerge'] = in_array('merge', $cascades);
$mapping['isCascadeDetach'] = in_array('detach', $cascades);
-
+
if (isset($mapping['type']) && $mapping['type'] === 'file') {
$mapping['file'] = true;
}
@@ -1160,7 +1225,7 @@ public function mapField(array $mapping)
&& ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
}
-
+
if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && CollectionHelper::isAtomic($mapping['strategy'])) {
throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
}
@@ -1514,7 +1579,7 @@ public function setFieldValue($document, $field, $value)
//so the proxy needs to be loaded first.
$document->__load();
}
-
+
$this->reflFields[$field]->setValue($document, $value);
}
@@ -1531,7 +1596,7 @@ public function getFieldValue($document, $field)
if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
$document->__load();
}
-
+
return $this->reflFields[$field]->getValue($document);
}
@@ -1543,8 +1608,6 @@ public function getFieldValue($document, $field)
* @return array The field mapping.
*
* @throws MappingException if the $fieldName is not found in the fieldMappings array
- *
- * @throws MappingException
*/
public function getFieldMapping($fieldName)
{
@@ -1554,6 +1617,26 @@ public function getFieldMapping($fieldName)
return $this->fieldMappings[$fieldName];
}
+ /**
+ * Gets the field mapping by its DB name.
+ * E.g. it returns identifier's mapping when called with _id.
+ *
+ * @param string $dbFieldName
+ *
+ * @return array
+ * @throws MappingException
+ */
+ public function getFieldMappingByDbFieldName($dbFieldName)
+ {
+ foreach ($this->fieldMappings as $mapping) {
+ if ($mapping['name'] == $dbFieldName) {
+ return $mapping;
+ }
+ }
+
+ throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
+ }
+
/**
* Check if the field is not null.
*
@@ -1701,7 +1784,7 @@ public function isIdGeneratorNone()
* value to use depending on the column type.
*
* @param array $mapping The version field mapping array
- *
+ *
* @throws LockException
*/
public function setVersionMapping(array &$mapping)
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
index e946c7bdbe..5f90fa8391 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
@@ -185,6 +185,11 @@ public function loadMetadataForClass($className, ClassMetadata $class)
}
}
+ // Set shard key after all fields to ensure we mapped all its keys
+ if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
+ $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
+ }
+
/** @var $method \ReflectionMethod */
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
/* Filter for the declaring class only. Callbacks from parent
@@ -240,6 +245,25 @@ private function addIndex(ClassMetadataInfo $class, $index, array $keys = array(
$class->addIndex($keys, $options);
}
+ /**
+ * @param ClassMetadataInfo $class
+ * @param ODM\ShardKey $shardKey
+ *
+ * @throws MappingException
+ */
+ private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
+ {
+ $options = array();
+ $allowed = array('unique', 'numInitialChunks');
+ foreach ($allowed as $name) {
+ if (isset($shardKey->$name)) {
+ $options[$name] = $shardKey->$name;
+ }
+ }
+
+ $class->setShardKey($shardKey->keys, $options);
+ }
+
/**
* Factory method for the Annotation Driver
*
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
index 45b78765c5..02ec95f40a 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
@@ -116,6 +116,9 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$this->addIndex($class, $index);
}
}
+ if (isset($xmlRoot->{'shard-key'})) {
+ $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
+ }
if (isset($xmlRoot['require-indexes'])) {
$class->setRequireIndexes('true' === (string) $xmlRoot['require-indexes']);
}
@@ -381,6 +384,41 @@ private function addIndex(ClassMetadataInfo $class, \SimpleXmlElement $xmlIndex)
$class->addIndex($keys, $options);
}
+ private function setShardKey(ClassMetadataInfo $class, \SimpleXmlElement $xmlShardkey)
+ {
+ $attributes = $xmlShardkey->attributes();
+
+ $keys = array();
+ $options = array();
+ foreach ($xmlShardkey->{'key'} as $key) {
+ $keys[(string) $key['name']] = isset($key['order']) ? (string)$key['order'] : 'asc';
+ }
+
+ if (isset($attributes['unique'])) {
+ $options['unique'] = ('true' === (string) $attributes['unique']);
+ }
+
+ if (isset($attributes['numInitialChunks'])) {
+ $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
+ }
+
+ if (isset($xmlShardkey->{'option'})) {
+ foreach ($xmlShardkey->{'option'} as $option) {
+ $value = (string) $option['value'];
+ if ($value === 'true') {
+ $value = true;
+ } elseif ($value === 'false') {
+ $value = false;
+ } elseif (is_numeric($value)) {
+ $value = preg_match('/^[-]?\d+$/', $value) ? (integer) $value : (float) $value;
+ }
+ $options[(string) $option['name']] = $value;
+ }
+ }
+
+ $class->setShardKey($keys, $options);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
index e9bd476e3b..a4fb2a0721 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
@@ -79,6 +79,9 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$class->addIndex($index['keys'], isset($index['options']) ? $index['options'] : array());
}
}
+ if (isset($element['shardKey'])) {
+ $this->setShardKey($class, $element['shardKey']);
+ }
if (isset($element['inheritanceType'])) {
$class->setInheritanceType(constant('Doctrine\ODM\MongoDB\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
}
@@ -335,4 +338,22 @@ protected function loadMappingFile($file)
{
return Yaml::parse(file_get_contents($file));
}
+
+ private function setShardKey(ClassMetadataInfo $class, array $shardKey)
+ {
+ $keys = $shardKey['keys'];
+ $options = array();
+
+ if (isset($shardKey['options'])) {
+ $allowed = array('unique', 'numInitialChunks');
+ foreach ($shardKey['options'] as $name => $value) {
+ if ( ! in_array($name, $allowed, true)) {
+ continue;
+ }
+ $options[$name] = $value;
+ }
+ }
+
+ $class->setShardKey($keys, $options);
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
index f847acb72c..96d37c6fcc 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
@@ -57,6 +57,16 @@ public static function mappingNotFound($className, $fieldName)
return new self("No mapping found for field '$fieldName' in class '$className'.");
}
+ /**
+ * @param string $className
+ * @param string $dbFieldName
+ * @return MappingException
+ */
+ public static function mappingNotFoundByDbName($className, $dbFieldName)
+ {
+ return new self("No mapping found for field by DB name '$dbFieldName' in class '$className'.");
+ }
+
/**
* @param string $document
* @param string $fieldName
@@ -255,4 +265,31 @@ public static function referenceManySortMustNotBeUsedWithNonSetCollectionStrateg
{
return new self("ReferenceMany's sort can not be used with addToSet and pushAll strategies, $strategy used in $className::$fieldName");
}
+
+ /**
+ * @param $subclassName
+ * @return MappingException
+ */
+ public static function shardKeyInSingleCollInheritanceSubclass($subclassName)
+ {
+ return new self("Shard key overriding in subclass is forbidden for single collection inheritance: $subclassName");
+ }
+
+ /**
+ * @param $className
+ * @return MappingException
+ */
+ public static function embeddedDocumentCantHaveShardKey($className)
+ {
+ return new self("Embedded document can't have shard key: $className");
+ }
+
+ /**
+ * @param string $className
+ * @return MappingException
+ */
+ public static function noIncrementFieldsAllowedInShardKey($className)
+ {
+ return new self("No increment fields allowed in the shard key: $className");
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/MongoDBException.php b/lib/Doctrine/ODM/MongoDB/MongoDBException.php
index b1ffc8cd46..e8ca840ecb 100644
--- a/lib/Doctrine/ODM/MongoDB/MongoDBException.php
+++ b/lib/Doctrine/ODM/MongoDB/MongoDBException.php
@@ -144,4 +144,50 @@ public static function invalidValueForType($type, $expected, $got)
}
return new self(sprintf('%s type requires value of type %s, %s given', $type, $expected, $gotType));
}
+
+ /**
+ * @param string $field
+ * @param string $className
+ * @return MongoDBException
+ */
+ public static function shardKeyFieldCannotBeChanged($field, $className)
+ {
+ return new self(sprintf('Shard key field "%s" cannot be changed. Class: %s', $field, $className));
+ }
+
+ /**
+ * @param string $field
+ * @param string $className
+ * @return MongoDBException
+ */
+ public static function shardKeyFieldMissing($field, $className)
+ {
+ return new self(sprintf('Shard key field "%s" is missing. Class: %s.', $field, $className));
+ }
+
+ /**
+ * @param string $dbName
+ * @param string $errorMessage
+ * @return MongoDBException
+ */
+ public static function failedToEnableSharding($dbName, $errorMessage)
+ {
+ return new self(sprintf('Failed to enable sharding for database "%s". Error from MongoDB: %s',
+ $dbName,
+ $errorMessage
+ ));
+ }
+
+ /**
+ * @param string $className
+ * @param string $errorMessage
+ * @return MongoDBException
+ */
+ public static function failedToEnsureDocumentSharding($className, $errorMessage)
+ {
+ return new self(sprintf('Failed to ensure sharding for document "%s". Error from MongoDB: %s',
+ $className,
+ $errorMessage
+ ));
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
index d5aefc65aa..3ebf81be96 100644
--- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
+++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
@@ -23,17 +23,18 @@
use Doctrine\MongoDB\CursorInterface;
use Doctrine\ODM\MongoDB\Cursor;
use Doctrine\ODM\MongoDB\DocumentManager;
-use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
use Doctrine\ODM\MongoDB\LockException;
use Doctrine\ODM\MongoDB\LockMode;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
+use Doctrine\ODM\MongoDB\MongoDBException;
use Doctrine\ODM\MongoDB\PersistentCollection;
use Doctrine\ODM\MongoDB\Proxy\Proxy;
use Doctrine\ODM\MongoDB\Query\CriteriaMerger;
use Doctrine\ODM\MongoDB\Query\Query;
use Doctrine\ODM\MongoDB\Types\Type;
use Doctrine\ODM\MongoDB\UnitOfWork;
+use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
/**
* The DocumentPersister is responsible for persisting documents.
@@ -276,24 +277,8 @@ public function executeUpserts(array $options = array())
}
foreach ($this->queuedUpserts as $oid => $document) {
- $data = $this->pb->prepareUpsertData($document);
-
- // Set the initial version for each upsert
- if ($this->class->isVersioned) {
- $versionMapping = $this->class->fieldMappings[$this->class->versionField];
- if ($versionMapping['type'] === 'int') {
- $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
- $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
- } elseif ($versionMapping['type'] === 'date') {
- $nextVersionDateTime = new \DateTime();
- $nextVersion = new \MongoDate($nextVersionDateTime->getTimestamp());
- $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
- }
- $data['$set'][$versionMapping['name']] = $nextVersion;
- }
-
try {
- $this->executeUpsert($data, $options);
+ $this->executeUpsert($document, $options);
$this->handleCollections($document, $options);
unset($this->queuedUpserts[$oid]);
} catch (\MongoException $e) {
@@ -304,23 +289,42 @@ public function executeUpserts(array $options = array())
}
/**
- * Executes a single upsert in {@link executeInserts}
+ * Executes a single upsert in {@link executeUpserts}
*
- * @param array $data
- * @param array $options
+ * @param object $document
+ * @param array $options
*/
- private function executeUpsert(array $data, array $options)
+ private function executeUpsert($document, array $options)
{
$options['upsert'] = true;
- $criteria = array('_id' => $data['$set']['_id']);
- unset($data['$set']['_id']);
+ $criteria = $this->getQueryForDocument($document);
+
+ $data = $this->pb->prepareUpsertData($document);
+
+ // Set the initial version for each upsert
+ if ($this->class->isVersioned) {
+ $versionMapping = $this->class->fieldMappings[$this->class->versionField];
+ if ($versionMapping['type'] === 'int') {
+ $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
+ $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
+ } elseif ($versionMapping['type'] === 'date') {
+ $nextVersionDateTime = new \DateTime();
+ $nextVersion = new \MongoDate($nextVersionDateTime->getTimestamp());
+ $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
+ }
+ $data['$set'][$versionMapping['name']] = $nextVersion;
+ }
+
+ foreach (array_keys($criteria) as $field) {
+ unset($data['$set'][$field]);
+ }
// Do not send an empty $set modifier
if (empty($data['$set'])) {
unset($data['$set']);
}
- /* If there are no modifiers remaining, we're upserting a document with
+ /* If there are no modifiers remaining, we're upserting a document with
* an identifier as its only field. Since a document with the identifier
* may already exist, the desired behavior is "insert if not exists" and
* NOOP otherwise. MongoDB 2.6+ does not allow empty modifiers, so $set
@@ -359,11 +363,18 @@ private function executeUpsert(array $data, array $options)
*/
public function update($document, array $options = array())
{
- $id = $this->uow->getDocumentIdentifier($document);
$update = $this->pb->prepareUpdateData($document);
- $id = $this->class->getDatabaseIdentifierValue($id);
- $query = array('_id' => $id);
+ $query = $this->getQueryForDocument($document);
+
+ foreach (array_keys($query) as $field) {
+ unset($update['$set'][$field]);
+ }
+
+ if (empty($update['$set'])) {
+ unset($update['$set']);
+ }
+
// Include versioning logic to set the new version value in the database
// and to ensure the version has not changed since this document object instance
@@ -416,8 +427,7 @@ public function update($document, array $options = array())
*/
public function delete($document, array $options = array())
{
- $id = $this->uow->getDocumentIdentifier($document);
- $query = array('_id' => $this->class->getDatabaseIdentifierValue($id));
+ $query = $this->getQueryForDocument($document);
if ($this->class->isLockable) {
$query[$this->class->lockField] = array('$exists' => false);
@@ -433,13 +443,15 @@ public function delete($document, array $options = array())
/**
* Refreshes a managed document.
*
- * @param array $id The identifier of the document.
+ * @param string $id
* @param object $document The document to refresh.
+ *
+ * @deprecated The first argument is deprecated.
*/
public function refresh($id, $document)
{
- $class = $this->dm->getClassMetadata(get_class($document));
- $data = $this->collection->findOne(array('_id' => $id));
+ $query = $this->getQueryForDocument($document);
+ $data = $this->collection->findOne($query);
$data = $this->hydratorFactory->hydrate($document, $data);
$this->uow->setOriginalDocumentData($document, $data);
}
@@ -521,6 +533,33 @@ public function loadAll(array $criteria = array(), array $sort = null, $limit =
return $cursor;
}
+ /**
+ * @param object $document
+ *
+ * @return array
+ * @throws MongoDBException
+ */
+ public function getShardKeyQuery($document)
+ {
+ if ( ! $this->class->isSharded()) {
+ return array();
+ }
+
+ $shardKey = $this->class->getShardKey();
+ $keys = array_keys($shardKey['keys']);
+ $data = $this->uow->getDocumentActualData($document);
+
+ $shardKeyQueryPart = array();
+ foreach ($keys as $key) {
+ $mapping = $this->class->getFieldMappingByDbFieldName($key);
+ $this->guardMissingShardKey($document, $key, $data);
+ $value = Type::getType($mapping['type'])->convertToDatabaseValue($data[$mapping['fieldName']]);
+ $shardKeyQueryPart[$key] = $value;
+ }
+
+ return $shardKeyQueryPart;
+ }
+
/**
* Wraps the supplied base cursor in the corresponding ODM class.
*
@@ -778,7 +817,7 @@ public function createReferenceManyInverseSideQuery(PersistentCollection $collec
private function loadReferenceManyWithRepositoryMethod(PersistentCollection $collection)
{
$cursor = $this->createReferenceManyWithRepositoryMethodCursor($collection);
- $mapping = $collection->getMapping();
+ $mapping = $collection->getMapping();
$documents = $cursor->toArray(false);
foreach ($documents as $key => $obj) {
if (CollectionHelper::isHash($mapping['strategy'])) {
@@ -1257,4 +1296,49 @@ private function handleCollections($document, $options)
$coll->takeSnapshot();
}
}
+
+ /**
+ * If the document is new, ignore shard key field value, otherwise throw an exception.
+ * Also, shard key field should be presented in actual document data.
+ *
+ * @param object $document
+ * @param string $shardKeyField
+ * @param array $actualDocumentData
+ *
+ * @throws MongoDBException
+ */
+ private function guardMissingShardKey($document, $shardKeyField, $actualDocumentData)
+ {
+ $dcs = $this->uow->getDocumentChangeSet($document);
+ $isUpdate = $this->uow->isScheduledForUpdate($document);
+
+ $fieldMapping = $this->class->getFieldMappingByDbFieldName($shardKeyField);
+ $fieldName = $fieldMapping['fieldName'];
+
+ if ($isUpdate && isset($dcs[$fieldName]) && $dcs[$fieldName][0] != $dcs[$fieldName][1]) {
+ throw MongoDBException::shardKeyFieldCannotBeChanged($shardKeyField, $this->class->getName());
+ }
+
+ if (!isset($actualDocumentData[$fieldName])) {
+ throw MongoDBException::shardKeyFieldMissing($shardKeyField, $this->class->getName());
+ }
+ }
+
+ /**
+ * Get shard key aware query for single document.
+ *
+ * @param object $document
+ *
+ * @return array
+ */
+ private function getQueryForDocument($document)
+ {
+ $id = $this->uow->getDocumentIdentifier($document);
+ $id = $this->class->getDatabaseIdentifierValue($id);
+
+ $shardKeyQueryPart = $this->getShardKeyQuery($document);
+ $query = array_merge(array('_id' => $id), $shardKeyQueryPart);
+
+ return $query;
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/SchemaManager.php b/lib/Doctrine/ODM/MongoDB/SchemaManager.php
index 0685391bf3..6cda2fe4d4 100644
--- a/lib/Doctrine/ODM/MongoDB/SchemaManager.php
+++ b/lib/Doctrine/ODM/MongoDB/SchemaManager.php
@@ -487,4 +487,98 @@ public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentInde
return true;
}
+
+ /**
+ * Ensure collections are sharded for all documents that can be loaded with the
+ * metadata factory.
+ *
+ * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
+ *
+ * @throws MongoDBException
+ */
+ public function ensureSharding(array $indexOptions = array())
+ {
+ foreach ($this->metadataFactory->getAllMetadata() as $class) {
+ if ($class->isMappedSuperclass || !$class->isSharded()) {
+ continue;
+ }
+
+ $this->ensureDocumentSharding($class->name, $indexOptions);
+ }
+ }
+
+ /**
+ * Ensure sharding for collection by document name.
+ *
+ * @param string $documentName
+ * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
+ *
+ * @throws MongoDBException
+ */
+ public function ensureDocumentSharding($documentName, array $indexOptions = array())
+ {
+ $class = $this->dm->getClassMetadata($documentName);
+ if ( ! $class->isSharded()) {
+ return;
+ }
+
+ $this->enableShardingForDbByDocumentName($documentName);
+
+ do {
+ $result = $this->runShardCollectionCommand($documentName);
+ $done = true;
+ $try = 0;
+
+ if ($result['ok'] != 1 && isset($result['proposedKey'])) {
+ $this->dm->getDocumentCollection($documentName)->ensureIndex($result['proposedKey'], $indexOptions);
+ $done = false;
+ $try++;
+ }
+ } while (!$done && $try < 2);
+
+ if ($result['ok'] != 1 && $result['errmsg'] !== 'already sharded') {
+ throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
+ }
+ }
+
+ /**
+ * Enable sharding for database which contains documents with given name.
+ *
+ * @param string $documentName
+ *
+ * @throws MongoDBException
+ */
+ public function enableShardingForDbByDocumentName($documentName)
+ {
+ $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
+ $adminDb = $this->dm->getConnection()->selectDatabase('admin');
+ $result = $adminDb->command(array('enableSharding' => $dbName));
+
+ if ($result['ok'] != 1 && $result['errmsg'] !== 'already enabled') {
+ throw MongoDBException::failedToEnableSharding($dbName, $result['errmsg']);
+ }
+ }
+
+ /**
+ * @param $documentName
+ *
+ * @return array
+ */
+ private function runShardCollectionCommand($documentName)
+ {
+ $class = $this->dm->getClassMetadata($documentName);
+ $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
+ $shardKey = $class->getShardKey();
+ $adminDb = $this->dm->getConnection()->selectDatabase('admin');
+
+ $result = $adminDb->command(
+ array(
+ 'shardCollection' => $dbName . '.' . $class->getCollection(),
+ 'key' => $shardKey['keys']
+ ),
+ $shardKey['options']
+ );
+
+ return $result;
+ }
}
diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
index 40dc282f81..7d9502149d 100644
--- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
+++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
@@ -175,10 +175,10 @@ class UnitOfWork implements PropertyChangedListener
* @var array
*/
private $collectionUpdates = array();
-
+
/**
* A list of documents related to collections scheduled for update or deletion
- *
+ *
* @var array
*/
private $hasScheduledCollections = array();
@@ -446,7 +446,7 @@ public function commit($document = null, array $options = array())
$this->collectionDeletions =
$this->visitedCollections =
$this->scheduledForDirtyCheck =
- $this->orphanRemovals =
+ $this->orphanRemovals =
$this->hasScheduledCollections = array();
}
@@ -2448,7 +2448,7 @@ public function clear($documentName = null)
$this->collectionUpdates =
$this->collectionDeletions =
$this->parentAssociations =
- $this->orphanRemovals =
+ $this->orphanRemovals =
$this->hasScheduledCollections = array();
} else {
$visited = array();
@@ -2523,11 +2523,11 @@ public function isCollectionScheduledForDeletion(PersistentCollection $coll)
{
return isset($this->collectionDeletions[spl_object_hash($coll)]);
}
-
+
/**
* INTERNAL:
* Unschedules a collection from being deleted when this UnitOfWork commits.
- *
+ *
* @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
*/
public function unscheduleCollectionDeletion(PersistentCollection $coll)
@@ -2561,11 +2561,11 @@ public function scheduleCollectionUpdate(PersistentCollection $coll)
$this->scheduleCollectionOwner($coll);
}
}
-
+
/**
* INTERNAL:
* Unschedules a collection from being updated when this UnitOfWork commits.
- *
+ *
* @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
*/
public function unscheduleCollectionUpdate(PersistentCollection $coll)
@@ -2577,7 +2577,7 @@ public function unscheduleCollectionUpdate(PersistentCollection $coll)
unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
}
}
-
+
/**
* Checks whether a PersistentCollection is scheduled for update.
*
@@ -2604,22 +2604,22 @@ public function getVisitedCollections($document)
? $this->visitedCollections[$oid]
: array();
}
-
+
/**
* INTERNAL:
* Gets PersistentCollections that are scheduled to update and related to $document
- *
+ *
* @param object $document
* @return array
*/
public function getScheduledCollections($document)
{
$oid = spl_object_hash($document);
- return isset($this->hasScheduledCollections[$oid])
+ return isset($this->hasScheduledCollections[$oid])
? $this->hasScheduledCollections[$oid]
: array();
}
-
+
/**
* Checks whether the document is related to a PersistentCollection
* scheduled for update or deletion.
@@ -2631,7 +2631,7 @@ public function hasScheduledCollections($document)
{
return isset($this->hasScheduledCollections[spl_object_hash($document)]);
}
-
+
/**
* Marks the PersistentCollection's top-level owner as having a relation to
* a collection scheduled for update or deletion.
@@ -2642,7 +2642,7 @@ public function hasScheduledCollections($document)
* If the collection is nested within atomic collection, it is immediately
* unscheduled and atomic one is scheduled for update instead. This makes
* calculating update data way easier.
- *
+ *
* @param PersistentCollection $coll
*/
private function scheduleCollectionOwner(PersistentCollection $coll)
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 06aca8fe1d..52ea03e0aa 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -25,6 +25,7 @@
performance
+ sharding
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php b/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
index bb2b5a449f..7951cd5501 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
@@ -6,17 +6,13 @@
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
use Doctrine\MongoDB\Connection;
+use Doctrine\ODM\MongoDB\UnitOfWork;
abstract class BaseTest extends \PHPUnit_Framework_TestCase
{
- /**
- * @var \Doctrine\ODM\MongoDB\DocumentManager
- */
+ /** @var DocumentManager */
protected $dm;
-
- /**
- * @var \Doctrine\ODM\MongoDB\UnitOfWork
- */
+ /** @var UnitOfWork */
protected $uow;
public function setUp()
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php
new file mode 100644
index 0000000000..87c5d97689
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php
@@ -0,0 +1,46 @@
+dm->getSchemaManager()->ensureDocumentSharding($class);
+
+ $collection = $this->dm->getDocumentCollection($class);
+ $indexes = $collection->getIndexInfo();
+ $stats = $this->dm->getDocumentDatabase($class)->command(array('collstats' => $collection->getName()));
+
+ $this->assertCount(2, $indexes);
+ $this->assertSame(array('k' => 1), $indexes[1]['key']);
+ $this->assertTrue($stats['sharded']);
+ }
+
+ /**
+ * @group sharding
+ */
+ public function testEnsureShardingForCollectionWithDocuments()
+ {
+ $class = 'Documents\Sharded\ShardedOne';
+ $collection = $this->dm->getDocumentCollection($class);
+ $doc = array('title' => 'hey', 'k' => 'hi');
+ $collection->insert($doc);
+
+ $this->dm->getSchemaManager()->ensureDocumentSharding($class);
+
+ $indexes = $collection->getIndexInfo();
+ $stats = $this->dm->getDocumentDatabase($class)->command(array('collstats' => $collection->getName()));
+
+ $this->assertCount(2, $indexes);
+ $this->assertSame(array('k' => 1), $indexes[1]['key']);
+ $this->assertTrue($stats['sharded']);
+ }
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
index 5dc2457580..64db477b85 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
@@ -41,6 +41,7 @@ public function testHintIsNotSetByDefault()
}
/**
+ * @group replication_lag
* @dataProvider provideReadPreferenceHints
*/
public function testHintIsSetOnQuery($readPreference, array $tags = null)
@@ -62,6 +63,7 @@ public function testHintIsSetOnQuery($readPreference, array $tags = null)
}
/**
+ * @group replication_lag
* @dataProvider provideReadPreferenceHints
*/
public function testHintIsSetOnCursor($readPreference, array $tags = null)
@@ -87,6 +89,7 @@ public function testHintIsSetOnCursor($readPreference, array $tags = null)
}
/**
+ * @group replication_lag
* @dataProvider provideReadPreferenceHints
*/
public function testHintIsSetOnPersistentCollection($readPreference, array $tags = null)
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
index 8f1f706b20..606772c687 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
@@ -246,6 +246,9 @@ public function testPrimeReferencesIgnoresInitializedProxyObjects()
$this->assertEquals(0, $invoked, 'Primer was not invoked when all references were already managed.');
}
+ /**
+ * @group replication_lag
+ */
public function testPrimeReferencesInvokesPrimer()
{
$group1 = new Group();
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php
new file mode 100644
index 0000000000..1d4672ee43
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php
@@ -0,0 +1,145 @@
+dm->getSchemaManager();
+ $schemaManager->ensureDocumentSharding('Documents\Sharded\ShardedOne');
+ }
+
+ /**
+ * @group sharding
+ */
+ public function testUpdateAfterSave()
+ {
+ $queries = array();
+ $this->logQueries($queries);
+
+ $o = new ShardedOne();
+ $this->dm->persist($o);
+ $this->dm->flush();
+
+ /** @var \Documents\Sharded\ShardedOne $o */
+ $o = $this->dm->find(get_class($o), $o->id);
+ $o->title = 'test2';
+ $this->dm->flush();
+
+ $lastQuery = end($queries);
+ $this->assertTrue($lastQuery['update']);
+ $this->assertContains('k', array_keys($lastQuery['query']));
+ $this->assertEquals($o->key, $lastQuery['query']['k']);
+ }
+
+ /**
+ * @group sharding
+ */
+ public function testUpsert()
+ {
+ $queries = array();
+ $this->logQueries($queries);
+
+ $o = new ShardedOne();
+ $o->id = new \MongoId();
+ $this->dm->persist($o);
+ $this->dm->flush();
+
+ $lastQuery = end($queries);
+ $this->assertTrue($lastQuery['update']);
+ $this->assertContains('k', array_keys($lastQuery['query']));
+ $this->assertEquals($o->key, $lastQuery['query']['k']);
+ }
+
+ /**
+ * @group sharding
+ */
+ public function testRemove()
+ {
+ $queries = array();
+ $this->logQueries($queries);
+
+ $o = new ShardedOne();
+ $this->dm->persist($o);
+ $this->dm->flush();
+ $this->dm->remove($o);
+ $this->dm->flush();
+
+ $lastQuery = end($queries);
+ $this->assertTrue($lastQuery['remove']);
+ $this->assertContains('k', array_keys($lastQuery['query']));
+ $this->assertEquals($o->key, $lastQuery['query']['k']);
+ }
+
+ /**
+ * @group sharding
+ */
+ public function testRefresh()
+ {
+ $queries = array();
+ $this->logQueries($queries);
+
+ $o = new ShardedOne();
+ $this->dm->persist($o);
+ $this->dm->flush();
+ $this->dm->refresh($o);
+
+ $lastQuery = end($queries);
+ $this->assertTrue($lastQuery['findOne']);
+ $this->assertContains('k', array_keys($lastQuery['query']));
+ $this->assertEquals($o->key, $lastQuery['query']['k']);
+ }
+
+ /**
+ * @group sharding
+ * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+ */
+ public function testUpdateWithShardKeyChangeException()
+ {
+ $o = new ShardedOne();
+ $this->dm->persist($o);
+ $this->dm->flush();
+
+ $o->key = 'testing2';
+ $this->dm->flush();
+ }
+
+ /**
+ * @group sharding
+ * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+ */
+ public function testUpdateWithUpsertTrue()
+ {
+ $o = new ShardedOne();
+ $this->dm->persist($o);
+ $this->dm->flush();
+
+ $o->key = 'testing2';
+ $this->dm->flush(null, array('upsert' => true));
+ }
+
+ /**
+ * Replace DM with the one with enabled query logging
+ *
+ * @param $queries
+ */
+ private function logQueries(&$queries)
+ {
+ $this->dm->getConnection()->getConfiguration()->setLoggerCallable(
+ function (array $log) use (&$queries) {
+ $queries[] = $log;
+ }
+ );
+ $this->dm = DocumentManager::create(
+ $this->dm->getConnection(),
+ $this->dm->getConfiguration()
+ );
+ }
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
index 48402fbad4..2d1dc62e15 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
@@ -20,6 +20,9 @@ public function setUp()
$this->dm->clear();
}
+ /**
+ * @group replication_lag
+ */
public function testHintIsNotSetByDefault()
{
$cursor = $this->dm->getRepository('Documents\User')
@@ -36,6 +39,7 @@ public function testHintIsNotSetByDefault()
}
/**
+ * @group replication_lag
* @dataProvider provideSlaveOkayHints
*/
public function testHintIsSetOnQuery($slaveOkay)
@@ -55,6 +59,7 @@ public function testHintIsSetOnQuery($slaveOkay)
}
/**
+ * @group replication_lag
* @dataProvider provideSlaveOkayHints
*/
public function testHintIsSetOnCursor($slaveOkay)
@@ -75,6 +80,7 @@ public function testHintIsSetOnCursor($slaveOkay)
}
/**
+ * @group replication_lag
* @dataProvider provideSlaveOkayHints
*/
public function testHintIsSetOnPersistentCollection($slaveOkay)
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
index 21731399dc..a1e3bdd53f 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
@@ -3,8 +3,6 @@
namespace Doctrine\ODM\MongoDB\Tests\Mapping;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
-use Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver;
-use Doctrine\ODM\MongoDB\Mapping\Driver\YamlDriver;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
abstract class AbstractMappingDriverTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
@@ -320,6 +318,23 @@ public function testIndexes($class)
return $class;
}
+
+ /**
+ * @depends testIndexes
+ * @param ClassMetadata $class
+ */
+ public function testShardKey($class)
+ {
+ $shardKey = $class->getShardKey();
+
+ $this->assertTrue(isset($shardKey['keys']['name']), 'Shard key is not mapped');
+ $this->assertEquals(1, $shardKey['keys']['name'], 'Wrong value for shard key');
+
+ $this->assertTrue(isset($shardKey['options']['unique']), 'Shard key option is not mapped');
+ $this->assertTrue($shardKey['options']['unique'], 'Shard key option has wrong value');
+ $this->assertTrue(isset($shardKey['options']['numInitialChunks']), 'Shard key option is not mapped');
+ $this->assertEquals(4096, $shardKey['options']['numInitialChunks'], 'Shard key option has wrong value');
+ }
}
/**
@@ -329,6 +344,7 @@ public function testIndexes($class)
* @ODM\DefaultDiscriminatorValue("default")
* @ODM\HasLifecycleCallbacks
* @ODM\Indexes(@ODM\Index(keys={"createdAt"="asc"},expireAfterSeconds=3600))
+ * @ODM\ShardKey(keys={"name"="asc"},unique=true,numInitialChunks=4096)
*/
class AbstractMappingDriverUser
{
@@ -519,5 +535,6 @@ public static function loadMetadata(ClassMetadata $metadata)
$metadata->addIndex(array('email' => 'desc'), array('unique' => true, 'dropDups' => true));
$metadata->addIndex(array('mysqlProfileId' => 'desc'), array('unique' => true, 'dropDups' => true));
$metadata->addIndex(array('createdAt' => 'asc'), array('expireAfterSeconds' => 3600));
+ $metadata->setShardKey(array('name' => 'asc'), array('unique' => true, 'numInitialChunks' => 4096));
}
}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
index 98bf3e04f8..0084012afc 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
@@ -3,7 +3,6 @@
namespace Doctrine\ODM\MongoDB\Tests\Mapping;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
-use Doctrine\ODM\MongoDB\Events;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
class AnnotationDriverTest extends AbstractMappingDriverTest
@@ -140,6 +139,15 @@ public function testGetClassNamesReturnsOnlyTheAppropriateClasses()
$this->assertNotContains($extraneousClassName, $classes);
}
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+ * @expectedExceptionMessage Embedded document can't have shard key
+ */
+ public function testEmbeddedClassCantHaveShardKey()
+ {
+ $this->dm->getClassMetadata(__NAMESPACE__ . '\AnnotationDriverEmbeddedWithShardKey');
+ }
+
protected function _loadDriverForCMSDocuments()
{
$annotationDriver = $this->_loadDriver();
@@ -187,3 +195,13 @@ class AnnotationDriverTestChild extends AnnotationDriverTestParent
/** @ODM\String */
public $bar;
}
+
+/**
+ * @ODM\EmbeddedDocument
+ * @ODM\ShardKey(keys={"foo"="asc"})
+ */
+class AnnotationDriverEmbeddedWithShardKey
+{
+ /** @ODM\String */
+ public $foo;
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
index 77a7f9ece1..0b09b05870 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
@@ -234,7 +234,7 @@ public function testSimpleReferenceRequiresTargetDocument()
'simple' => true,
));
}
-
+
/**
* @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
* @expectedExceptionMessage atomicSet collection strategy can be used only in top level document, used in stdClass::many
@@ -328,6 +328,91 @@ public function testReferenceManySortMustNotBeUsedWithNonSetCollectionStrategy()
'sort' => array('foo' => 1)
));
}
+
+ public function testSetShardKeyForClassWithoutInheritance()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->setShardKey(array('id' => 'asc'));
+
+ $shardKey = $cm->getShardKey();
+
+ $this->assertEquals(array('id' => 1), $shardKey['keys']);
+ }
+
+ public function testSetShardKeyForClassWithSingleCollectionInheritance()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION;
+ $cm->setShardKey(array('id' => 'asc'));
+
+ $shardKey = $cm->getShardKey();
+
+ $this->assertEquals(array('id' => 1), $shardKey['keys']);
+ }
+
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+ * @expectedExceptionMessage Shard key overriding in subclass is forbidden for single collection inheritance
+ */
+ public function testSetShardKeyForClassWithSingleCollectionInheritanceWhichAlreadyHasIt()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->setShardKey(array('id' => 'asc'));
+ $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION;
+
+ $cm->setShardKey(array('foo' => 'asc'));
+ }
+
+ public function testSetShardKeyForClassWithCollPerClassInheritance()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
+ $cm->setShardKey(array('id' => 'asc'));
+
+ $shardKey = $cm->getShardKey();
+
+ $this->assertEquals(array('id' => 1), $shardKey['keys']);
+ }
+
+ public function testIsNotShardedIfThereIsNoShardKey()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+
+ $this->assertFalse($cm->isSharded());
+ }
+
+ public function testIsShardedIfThereIsAShardKey()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->setShardKey(array('id' => 'asc'));
+
+ $this->assertTrue($cm->isSharded());
+ }
+
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+ * @expectedExceptionMessage Embedded document can't have shard key: stdClass
+ */
+ public function testEmbeddedDocumentCantHaveShardKey()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->isEmbeddedDocument = true;
+ $cm->setShardKey(array('id' => 'asc'));
+ }
+
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+ * @expectedExceptionMessage No increment fields allowed in the shard key
+ */
+ public function testNoIncrementFieldsAllowedInShardKey()
+ {
+ $cm = new ClassMetadataInfo('stdClass');
+ $cm->mapField(array(
+ 'fieldName' => 'inc',
+ 'type' => 'increment'
+ ));
+ $cm->setShardKey(array('inc'));
+ }
}
class TestCustomRepositoryClass extends DocumentRepository
@@ -344,7 +429,7 @@ class EmbeddedAssociationsCascadeTest
{
/** @ODM\Id */
public $id;
-
+
/** @ODM\EmbedOne(targetDocument="Documents\Address") */
public $address;
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
index df122515b1..b420737b3d 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
@@ -4,7 +4,6 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
-use Doctrine\ODM\MongoDB\Events;
class ClassMetadataTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
{
@@ -31,6 +30,7 @@ public function testClassMetadataInstanceSerialization()
$cm->setFile('customFileProperty');
$cm->setDistance('customDistanceProperty');
$cm->setSlaveOkay(true);
+ $cm->setShardKey(array('_id' => '1'));
$cm->setCollectionCapped(true);
$cm->setCollectionMax(1000);
$cm->setCollectionSize(500);
@@ -57,6 +57,7 @@ public function testClassMetadataInstanceSerialization()
$this->assertEquals('customFileProperty', $cm->file);
$this->assertEquals('customDistanceProperty', $cm->distance);
$this->assertTrue($cm->slaveOkay);
+ $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $cm->getShardKey());
$mapping = $cm->getFieldMapping('phonenumbers');
$this->assertEquals('Documents\Bar', $mapping['targetDocument']);
$this->assertTrue($cm->getCollectionCapped());
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php
new file mode 100644
index 0000000000..5324863a39
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php
@@ -0,0 +1,127 @@
+factory = new ClassMetadataFactory();
+ $this->factory->setDocumentManager($this->dm);
+ $this->factory->setConfiguration($this->dm->getConfiguration());
+ }
+
+
+ public function testShardKeyFromMappedSuperclass()
+ {
+ $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSubclass');
+
+ $this->assertTrue($class->isSharded());
+ $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+ }
+
+ public function testShardKeySingleCollectionInheritance()
+ {
+ $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSingleCollInheritance2');
+
+ $this->assertTrue($class->isSharded());
+ $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+ }
+
+ /**
+ * @expectedException Doctrine\ODM\MongoDB\Mapping\MappingException
+ */
+ public function testShardKeySingleCollectionInheritanceOverriding()
+ {
+ $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSingleCollInheritance3');
+ }
+
+ public function testShardKeyCollectionPerClassInheritance()
+ {
+ $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedCollectionPerClass2');
+
+ $this->assertTrue($class->isSharded());
+ $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+ }
+
+ public function testShardKeyCollectionPerClassInheritanceOverriding()
+ {
+ $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedCollectionPerClass3');
+
+ $this->assertTrue($class->isSharded());
+ $this->assertEquals(array('keys' => array('_id' => 'hashed'), 'options' => array()), $class->getShardKey());
+ }
+}
+
+
+/**
+ * @ODM\MappedSuperclass
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedSuperclass
+{
+ /** @ODM\String */
+ private $name;
+}
+
+/** @ODM\Document */
+class ShardedSubclass extends ShardedSuperclass
+{
+ /** @ODM\Id */
+ private $id;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\InheritanceType("SINGLE_COLLECTION")
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedSingleCollInheritance1
+{
+ /** @ODM\Id */
+ private $id;
+}
+
+/**
+ * @ODM\Document
+ */
+class ShardedSingleCollInheritance2 extends ShardedSingleCollInheritance1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="hashed"})
+ */
+class ShardedSingleCollInheritance3 extends ShardedSingleCollInheritance1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\InheritanceType("COLLECTION_PER_CLASS")
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedCollectionPerClass1
+{
+ /** @ODM\Id */
+ private $id;
+}
+
+/**
+ * @ODM\Document
+ */
+class ShardedCollectionPerClass2 extends ShardedCollectionPerClass1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="hashed"})
+ */
+class ShardedCollectionPerClass3 extends ShardedCollectionPerClass1
+{}
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
index e92cc67a1e..89865d2b4c 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
@@ -2,6 +2,7 @@
namespace Doctrine\ODM\MongoDB\Tests\Mapping;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
use Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver;
class XmlMappingDriverTest extends AbstractMappingDriverTest
@@ -10,4 +11,21 @@ protected function _loadDriver()
{
return new XmlDriver(__DIR__ . DIRECTORY_SEPARATOR . 'xml');
}
+
+ public function testSetShardKeyOptionsByAttributes()
+ {
+ $class = new ClassMetadataInfo('doc');
+ $driver = $this->_loadDriver();
+ $element = new \SimpleXmlElement('');
+
+ /** @uses XmlDriver::setShardKey */
+ $m = new \ReflectionMethod(get_class($driver), 'setShardKey');
+ $m->setAccessible(true);
+ $m->invoke($driver, $class, $element);
+
+ $this->assertTrue($class->isSharded());
+ $shardKey = $class->getShardKey();
+ $this->assertSame(array('unique' => true, 'numInitialChunks' => 4096), $shardKey['options']);
+ $this->assertSame(array('_id' => 1), $shardKey['keys']);
+ }
}
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
index 25e74305b0..2a3cd81c6d 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
@@ -67,5 +67,10 @@
+
+
+
+
+
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
index 5291669693..e5718d4e34 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
@@ -46,6 +46,12 @@ Doctrine\ODM\MongoDB\Tests\Mapping\AbstractMappingDriverUser:
options:
unique: true
dropDups: true
+ shardKey:
+ keys:
+ name: asc
+ options:
+ unique: true
+ numInitialChunks: 4096
referenceOne:
address:
targetDocument: Address
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php
new file mode 100644
index 0000000000..891865267d
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php
@@ -0,0 +1,113 @@
+int = 1;
+ $o->string = 'hi';
+ $o->bool = true;
+ $o->float = 1.2;
+
+ /** @var DocumentPersister $persister */
+ $persister = $this->uow->getDocumentPersister(get_class($o));
+
+ $this->assertSame(
+ array('int' => $o->int, 'string' => $o->string, 'bool' => $o->bool, 'float' => $o->float),
+ $persister->getShardKeyQuery($o)
+ );
+ }
+
+ public function testGetShardKeyQueryObjects()
+ {
+ $o = new ShardedByObjects();
+ $o->oid = '54ca2c4c81fec698130041a7';
+ $o->bin = 'hi';
+ $o->date = new \DateTime();
+
+ /** @var DocumentPersister $persister */
+ $persister = $this->uow->getDocumentPersister(get_class($o));
+
+ $shardKeyQuery = $persister->getShardKeyQuery($o);
+
+ $this->assertInstanceOf('MongoId', $shardKeyQuery['oid']);
+ $this->assertSame($o->oid, $shardKeyQuery['oid']->{'$id'});
+
+ $this->assertInstanceOf('MongoBinData', $shardKeyQuery['bin']);
+ $this->assertSame($o->bin, $shardKeyQuery['bin']->bin);
+
+ $this->assertInstanceOf('MongoDate', $shardKeyQuery['date']);
+ $this->assertSame($o->date->getTimestamp(), $shardKeyQuery['date']->sec);
+ $this->assertSame(0, $shardKeyQuery['date']->usec);
+ }
+
+ public function testShardById()
+ {
+ $o = new ShardedById();
+ $o->identifier = new \MongoId();
+
+ /** @var DocumentPersister $persister */
+ $persister = $this->uow->getDocumentPersister(get_class($o));
+ $shardKeyQuery = $persister->getShardKeyQuery($o);
+
+ $this->assertSame(array('_id' => $o->identifier), $shardKeyQuery);
+ }
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"int"="asc","string"="asc","bool"="asc","float"="asc"})
+ */
+class ShardedByScalars
+{
+ /** @ODM\Id */
+ public $id;
+
+ /** @ODM\Int */
+ public $int;
+
+ /** @ODM\String */
+ public $string;
+
+ /** @ODM\Boolean */
+ public $bool;
+
+ /** @ODM\Float */
+ public $float;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"oid"="asc","bin"="asc","date"="asc"})
+ */
+class ShardedByObjects
+{
+ /** @ODM\Id */
+ public $id;
+
+ /** @ODM\ObjectId */
+ public $oid;
+
+ /** @ODM\Bin */
+ public $bin;
+
+ /** @ODM\Date */
+ public $date;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedById
+{
+ /** @ODM\Id */
+ public $identifier;
+}
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
index dde3c2753b..c32b6b5d1b 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
@@ -295,6 +295,149 @@ public function testDropDatabases()
$this->schemaManager->dropDatabases();
}
+ public function testEnsureDocumentSharding()
+ {
+ $dbName = DOCTRINE_MONGODB_DATABASE;
+ $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+ $collectionName = $classMetadata->getCollection();
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn($dbName);
+ $adminDBMock = $this->getMockDatabase();
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+ $adminDBMock
+ ->expects($this->at(0))
+ ->method('command')
+ ->with(array('enableSharding' => $dbName))
+ ->willReturn(array('ok' => 1));
+ $adminDBMock
+ ->expects($this->at(1))
+ ->method('command')
+ ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+ ->willReturn(array('ok' => 1));
+
+ $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+ }
+
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+ * @expectedExceptionMessage Failed to ensure sharding for document
+ */
+ public function testEnsureDocumentShardingThrowsExceptionIfThereWasAnError()
+ {
+ $dbName = DOCTRINE_MONGODB_DATABASE;
+ $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+ $collectionName = $classMetadata->getCollection();
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn($dbName);
+ $adminDBMock = $this->getMockDatabase();
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+ $adminDBMock
+ ->expects($this->at(0))
+ ->method('command')
+ ->with(array('enableSharding' => $dbName))
+ ->willReturn(array('ok' => 1));
+ $adminDBMock
+ ->expects($this->at(1))
+ ->method('command')
+ ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+ ->willReturn(array('ok' => 0, 'errmsg' => 'scary error'));
+
+ $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+ }
+
+ public function testEnsureDocumentShardingIgnoresAlreadyShardedError()
+ {
+ $dbName = DOCTRINE_MONGODB_DATABASE;
+ $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+ $collectionName = $classMetadata->getCollection();
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn($dbName);
+ $adminDBMock = $this->getMockDatabase();
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+ $adminDBMock
+ ->expects($this->at(0))
+ ->method('command')
+ ->with(array('enableSharding' => $dbName))
+ ->willReturn(array('ok' => 1));
+ $adminDBMock
+ ->expects($this->at(1))
+ ->method('command')
+ ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+ ->willReturn(array('ok' => 0, 'errmsg' => 'already sharded'));
+
+ $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+ }
+
+ public function testEnableShardingForDb()
+ {
+ $adminDBMock = $this->getMockDatabase();
+ $adminDBMock
+ ->expects($this->once())
+ ->method('command')
+ ->with(array('enableSharding' => 'db'))
+ ->willReturn(array('ok' => 1));
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn('db');
+ $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+ $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+ }
+
+ /**
+ * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+ * @expectedExceptionMessage Failed to enable sharding for database
+ */
+ public function testEnableShardingForDbThrowsExceptionInCaseOfError()
+ {
+ $adminDBMock = $this->getMockDatabase();
+ $adminDBMock
+ ->expects($this->once())
+ ->method('command')
+ ->with(array('enableSharding' => 'db'))
+ ->willReturn(array('ok' => 0, 'errmsg' => 'scary error'));
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn('db');
+ $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+ $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+ }
+
+ public function testEnableShardingForDbIgnoresAlreadyShardedError()
+ {
+ $adminDBMock = $this->getMockDatabase();
+ $adminDBMock
+ ->expects($this->once())
+ ->method('command')
+ ->with(array('enableSharding' => 'db'))
+ ->willReturn(array('ok' => 0, 'errmsg' => 'already enabled'));
+ $connMock = $this->getMockConnection();
+ $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+ $this->dm->connection = $connMock;
+ $dbMock = $this->getMockDatabase();
+ $dbMock->method('getName')->willReturn('db');
+ $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+ $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+ }
+
private function getMockCollection()
{
return $this->getMockBuilder('Doctrine\MongoDB\Collection')
@@ -345,4 +488,11 @@ private function getMockUnitOfWork()
return $uow;
}
+
+ private function getMockConnection()
+ {
+ return $this->getMockBuilder('Doctrine\MongoDB\Connection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
}
diff --git a/tests/Documents/Sharded/ShardedOne.php b/tests/Documents/Sharded/ShardedOne.php
new file mode 100644
index 0000000000..0fa5aaf89c
--- /dev/null
+++ b/tests/Documents/Sharded/ShardedOne.php
@@ -0,0 +1,21 @@
+