Added Sharding support

Added shard_key meta, so save() and update() passes shard keys
to the pymongo query.  Also made shard key fields immutable.

Closes #388 and #389
This commit is contained in:
Ross Lawley 2011-12-12 16:13:19 +00:00
parent 9b3899476c
commit 11daf706df
5 changed files with 51 additions and 6 deletions

View File

@ -89,3 +89,6 @@ that much better:
* Ankhbayar * Ankhbayar
* Jan Schrewe * Jan Schrewe
* David Koblas * David Koblas
* Crittercism
* Alvin Liang
* andrewmlevy

View File

@ -5,6 +5,7 @@ Changelog
Changes in dev Changes in dev
============== ==============
- Added sharding support
- Added pymongo 2.1 support - Added pymongo 2.1 support
- Fixed Abstract documents can now declare indexes - Fixed Abstract documents can now declare indexes
- Added db_alias support to individual documents - Added db_alias support to individual documents

View File

@ -476,8 +476,29 @@ subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
first_post = BlogPost.objects.order_by("+published_date").first() first_post = BlogPost.objects.order_by("+published_date").first()
assert first_post.title == "Blog Post #1" assert first_post.title == "Blog Post #1"
Shard keys
==========
If your collection is sharded, then you need to specify the shard key as a tuple,
using the :attr:`shard_key` attribute of :attr:`-mongoengine.Document.meta`.
This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing
:class:`-mongoengine.Document` instance::
class LogEntry(Document):
machine = StringField()
app = StringField()
timestamp = DateTimeField()
data = StringField()
meta = {
'shard_key': ('machine', 'timestamp',)
}
Document inheritance Document inheritance
==================== ====================
To create a specialised type of a :class:`~mongoengine.Document` you have To create a specialised type of a :class:`~mongoengine.Document` you have
defined, you may subclass it and add any extra fields or methods you may need. defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of As this is new class is not a direct subclass of

View File

@ -724,6 +724,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
class BaseDocument(object): class BaseDocument(object):
_dynamic = False _dynamic = False
_created = True
def __init__(self, **values): def __init__(self, **values):
signals.pre_init.send(self.__class__, document=self, values=values) signals.pre_init.send(self.__class__, document=self, values=values)
@ -757,6 +758,7 @@ class BaseDocument(object):
if self._dynamic: if self._dynamic:
for key, value in dynamic_data.items(): for key, value in dynamic_data.items():
setattr(self, key, value) setattr(self, key, value)
signals.post_init.send(self.__class__, document=self) signals.post_init.send(self.__class__, document=self)
def __setattr__(self, name, value): def __setattr__(self, name, value):
@ -784,6 +786,11 @@ class BaseDocument(object):
if hasattr(self, '_changed_fields'): if hasattr(self, '_changed_fields'):
self._mark_as_changed(name) self._mark_as_changed(name)
return return
if not self._created and name in self._meta.get('shard_key', tuple()):
from queryset import OperationError
raise OperationError("Shard Keys are immutable. Tried to update %s" % name)
super(BaseDocument, self).__setattr__(name, value) super(BaseDocument, self).__setattr__(name, value)
def __expand_dynamic_values(self, name, value): def __expand_dynamic_values(self, name, value):
@ -912,6 +919,7 @@ class BaseDocument(object):
obj = cls(**data) obj = cls(**data)
obj._changed_fields = changed_fields obj._changed_fields = changed_fields
obj._created = False
return obj return obj
def _mark_as_changed(self, key): def _mark_as_changed(self, key):

View File

@ -171,6 +171,7 @@ class Document(BaseDocument):
doc = self.to_mongo() doc = self.to_mongo()
created = force_insert or '_id' not in doc created = force_insert or '_id' not in doc
try: try:
collection = self.__class__.objects._collection collection = self.__class__.objects._collection
if created: if created:
@ -181,10 +182,18 @@ class Document(BaseDocument):
else: else:
object_id = doc['_id'] object_id = doc['_id']
updates, removals = self._delta() updates, removals = self._delta()
# Need to add shard key to query, or you get an error
select_dict = {'_id': object_id}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
actual_key = self._db_field_map.get(k, k)
select_dict[actual_key] = doc[actual_key]
if updates: if updates:
collection.update({'_id': object_id}, {"$set": updates}, upsert=True, safe=safe, **write_options) collection.update(select_dict, {"$set": updates}, upsert=True, safe=safe, **write_options)
if removals: if removals:
collection.update({'_id': object_id}, {"$unset": removals}, upsert=True, safe=safe, **write_options) collection.update(select_dict, {"$unset": removals}, upsert=True, safe=safe, **write_options)
cascade = self._meta.get('cascade', True) if cascade is None else cascade cascade = self._meta.get('cascade', True) if cascade is None else cascade
if cascade: if cascade:
@ -238,7 +247,12 @@ class Document(BaseDocument):
if not self.pk: if not self.pk:
raise OperationError('attempt to update a document not yet saved') raise OperationError('attempt to update a document not yet saved')
return self.__class__.objects(pk=self.pk).update_one(**kwargs) # Need to add shard key to query, or you get an error
select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
select_dict[k] = getattr(self, k)
return self.__class__.objects(**select_dict).update_one(**kwargs)
def delete(self, safe=False): def delete(self, safe=False):
"""Delete the :class:`~mongoengine.Document` from the database. This """Delete the :class:`~mongoengine.Document` from the database. This
@ -248,10 +262,8 @@ class Document(BaseDocument):
""" """
signals.pre_delete.send(self.__class__, document=self) signals.pre_delete.send(self.__class__, document=self)
id_field = self._meta['id_field']
object_id = self._fields[id_field].to_mongo(self[id_field])
try: try:
self.__class__.objects(**{id_field: object_id}).delete(safe=safe) self.__class__.objects(pk=self.pk).delete(safe=safe)
except pymongo.errors.OperationFailure, err: except pymongo.errors.OperationFailure, err:
message = u'Could not delete document (%s)' % err.message message = u'Could not delete document (%s)' % err.message
raise OperationError(message) raise OperationError(message)