Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
356 lines (226 sloc) 9.53 KB

Querying

MongoDB offers a lot of flexibility when querying for documents in the database. Simon tries to expose that flexibility in easy to use ways.

To use one of MongoDB's operators, append it to the name of the field you want to apply it to, separated by a double underscore (__). Simon will automatically translate it into the correct query syntax.

# match users where name is not equal to 'Simon'
users = User.find(name__ne='Simon')

# match users where score is greater than 1000
users = User.find(score__gt=1000)

# match users created in 2012
from datetime import datetime
jan_1_2012 = datetime(2012, 1, 1)
jan_1_2013 = datetime(2013, 1, 1)
users = User.find(created__gte=jan_1_2012, created__lt=jan_1_2013)

There's queries will be translated to:

users = db.users.find({name: {$ne: 'Simon'}})

users = db.users.find({score: {$gt: 1000}})

jan_1_2012 = new Date(2012, 1, 1)
jan_1_2013 = new Date(2013, 1, 1)
users = db.users.find({created: {$gte: jan_1_2012, $lt: jan_1_2013}})

More information about all of the operators offered by MongoDB is available in the MongoDB docs.

Comparison Operators

The full list of comparison operators available is:

gt

Matches documents where the field's value is greater than the specified value.

users = User.find(score__gt=1000)
gte

Matches documents where the field's value is greater than or equal to the specified value.

users = User.find(score__gte=1000)
lt

Matches documents where the field's value is less than the specified value.

users = User.find(score__lt=1000)
lte

Matches documents where the field's value is less than or equal to the specified value.

users = User.find(score__lte=1000)
ne

Matches documents where the field's value is not equal to the specified value.

users = User.find(name__ne='Simon')
in

Matches documents where the field's value is equal to any of the values in the specified list.

users = User.find(name__in=['Alvin', 'Simon', 'Theodore'])
nin

Matches documents where the field's value is not equal to any of the values in the specified list.

users = User.find(name__nin=['Alvin', 'Simon', 'Theodore'])
all

Matches documents where the field holds a list containing all of the specified elements.

users = User.find(friends__all=['Alvin', 'Theodore'])

Element Operators

The full list of element operators available is:

exists

Matches documents where the field's existence matches the specified value.

users = User.find(email__exists=True)

Array Operators

The full list of array operators available is:

elemMatch

Matches documents where the field is a list matching the specified query.

users = User.find(addresses__elemmatch={'state': 'NY'})
size

Matches documents where the field is a list of the specified length.

users = User.find(fields__size=2)

Geospatial Operators

One of the most powerful ways to query with MongoDB is through geospatial querying. Unlike the operators discussed thus far, Simon exposes the geospatial operators through convenience methods that help harness the full potential of each operator.

Before you can use any of these operators, you will need to create a :data:`two-dimensional index <pymongo:pymongo.GEO2D>`.

db.users.ensureIndex({location: '2d'})

The convenience methods can be used by importing the geo module.

from simon import geo
near

Matches documents from nearest to farthest with respect to the specified point.

users = User.find(location=geo.near([x, y]))
within

Matches documents contained within the specified shape.

users = User.find(location=geo.within('box', [x1, y1], [x2, y2]))

While :meth:`~simon.geo.within` can be used on its own, the following methods make it even easier.

box

Matches documents within the specified rectangular shape.

users = User.find(location=geo.box([x1, y1], [x2, y2]))
polygon

Matches documents within the specified polygonal shape.

users = User.find(location=geo.polygon([x1, y1], [x2, y2], [x3, y3]))
center

Matches documents within the specified circular shape. Note the center operator is accessed through the :meth:`~simon.geo.circle` method.

center = [x, y]
users = User.find(location=geo.circle(center, radius))

Here's a quick run through of these queries in the mongo Shell:

users = db.users.find({location: {$near: [x, y]}})

users = db.users.find({location: {$within: {$box: [[x1, y1], [x2, y2]]}}})

users = db.users.find({location: {$within: {$polygon: [[x1, y1], [x2, y2], [x3, y3]]}}})

users = db.users.find({location: {$within: {$center: [[x, y], radius]}}})

The full list of options offered by each method can be found in the :ref:`geo` section of :doc:`api`.

Logical Operators

Sometimes more complex queries require combining conditions with logical operators, such as AND, OR, and NOT.

not

Performs a logical NOT operation on the specified expression.

users = User.find(score__not__gt=1000)

To perform this query in the mongo Shell:

users = db.users.find({score: {$not: {$gt: 1000}}})

Using the AND and OR operators with Simon requires the assistance of :class:`~simon.query.Q` objects. Fortunately they work just like any other query with Simon. Instead of passing the the query directly to a method like :meth:`~simon.Model.find`, however, the query is passed to :class:`~simon.query.Q`.

from simon.query import Q
query = Q(name='Simon')

The new object is then combined with one or more additional :class:`~simon.query.Q` objects, the end result of which is then passed to :meth:`~simon.Model.find`. :class:`~simon.query.Q` objects are combined using bitwise and (&) and or (|) to represent logical AND and OR, respectively.

# match users where name is equal to 'Simon' AND score is greater
# than 1000
users = User.find(Q(name='Simon') & Q(score__gt=1000))

# match users where name is equal to 'Simon' AND score is greater
# than 1000, OR name is either 'Alvin' or 'Theodore'
users = User.find(Q(name='Simon', score__gt=1000) | Q(name__in=['Alvin', 'Theodore']))

# match users who have no friends
users = User.find(Q(friends__exists=False) | Q(friends__size=0))

Any number of :class:`~simon.query.Q` objects can be chained together. Be careful, however, as chaining together a lot of queries through different operators can result in deeply nested queries, which may become inefficient.

Here's how these queries would look in the mongo Shell:

users = db.users.find({$and: [{name: 'Simon'}, {score: {$gt: 1000}}]})

users = db.users.find({$or: [{name: 'Simon', score: {$gt: 1000}}, {name: {$in: ['Alvin', 'Theodore']}}]})

users = db.users.find({$or: [{friends: {$exists: false}}, {friends: {$size: 0}}]})

Exceptions

When using :meth:`~simon.Model.get` to retrieve a document, there are two potential exceptions that may be raised. When one of these exceptions is raised, it will be raised as part of the model class being queried.

Assume the following documents for all examples below.

:class:`~simon.exceptions.MultipleDocumentsFound`

This exception is raised when multiple documents match the specified query.

User.create(name='Simon', email='simon@example.com')
User.create(name='Simon', email='simon@example.org')

try:
    user = User.get(name='Simon')
except User.MultipleDocumentsFound:
    """Handle the exception here"""
else:
    """Only one User was found"""
:class:`~simon.exceptions.NoDocumentFound`

This exception is raised when no documents match the specified query.

try:
    user = User.get(name='Alvin')
except User.NoDocumentFound:
    """Handle the exception here"""
else:
    """Only one User was found"""

In the case of :class:`~simon.exceptions.NoDocumentFound`, there may be times when the way to handle the exception would be to create the document. A common pattern would:

try:
    user = User.get(name='Simon')
except User.NoDocumentFound:
    user = User.create(name='Simon')

Rather than making you use this pattern over and over, Simon does it for you, inside the :meth:`~simon.Model.get_or_create` method. Not only will :meth:`~simon.Model.get_or_create` do this, it will also let you know if it had to create the document.

user, created = User.get_or_create(name='Simon')
# user will be the newly created document and created will be True

user, created = User.get_or_create(name='Simon')
# user will be loaded from the database and created will be False

If multiple documents match the query, :class:`~simon.exceptions.MultipleDocumentsFound` will still be raised.