Статьи

Да, каждый драйвер MongoDB поддерживает каждую команду

 

Этот пост является ответом на постоянный вопрос о драйверах MongoDB, который я получаю: «Поддерживает ли драйвер X функцию Y?» Ответ почти всегда «да», но вы не можете знать это, если не понимаете команды MongoDB.

Существует только четыре вида операций, которые драйвер MongoDB может выполнять на сервере: вставка, обновление, удаление, запрос и команды.

Почти два года назад моя коллега Кристина писала о « Почему командные помощники сосут », и она все еще права: если вы используете только удобные методы, не понимая объединяющую концепцию «команды», вы излишне привязаны к API конкретного драйвера и вы не знаете, как на самом деле работает MongoDB.

Итак, давайте сделаем поп-викторину:

  1. Какие драйверы MongoDB поддерживают Aggregation Framework?

  2. Какие поддерживают «групповые» операции?

  3. Какие драйверы совместимы с функцией mapreduce MongoDB?

  4. Какие драйверы позволяют запускать «считать» или «различать» в коллекции?

Если вы ответили «все из них», вы правы — каждый драйвер поддерживает команды, а все функции, о которых я спрашивал, — это команды.

Давайте рассмотрим три драйвера MongoDB для Python и покажем примеры использования distinctкоманды в каждом.

PyMongo

У PyMongo есть два удобных метода для distinct. Один в Collectionклассе, другой на Cursor:

>>> from pymongo import MongoClient
>>> db = MongoClient().test
>>> db.test_collection.distinct('my_key')
[1.0, 2.0, 3.0]
>>> db.test_collection.find().distinct('my_key')
[1.0, 2.0, 3.0]

Но все это сводится к одной и той же команде MongoDB. Мы можем посмотреть его аргументы в Справочнике по командам MongoDB и увидеть, что отдельный принимает форму:

{ distinct: collection, key: <field>, query: <query> }

Итак, давайте использовать общий commandметод PyMongo для distinctнепосредственного запуска . Мы будем передавать collectionи keyаргументы и опускаем query. Нам нужно использовать SONкласс PyMongo, чтобы убедиться, что мы передаем аргументы в правильном порядке:

>>> from bson import SON
>>> db.command(SON([('distinct', 'test_collection'), ('key', 'my_key')]))
{u'ok': 1.0,
 u'stats': {u'cursor': u'BasicCursor',
            u'n': 3,
            u'nscanned': 3,
            u'nscannedObjects': 3,
            u'timems': 0},
 u'values': [1.0, 2.0, 3.0]}

Ответ в values.

двигатель

Мой асинхронный драйвер для Tornado и MongoDB, называемый Motor , поддерживает аналогичные удобства для distinct. У него есть оба MotorCollection.distinctметода:

>>> from tornado.ioloop import IOLoop
>>> from tornado import gen
>>> import motor
>>> from motor import MotorConnection
>>> db = MotorConnection().open_sync().test
>>> @gen.engine
... def f():
...     print (yield motor.Op(db.test_collection.distinct, 'my_key'))
...     IOLoop.instance().stop()
... 
>>> f()
>>> IOLoop.instance().start()
[1.0, 2.0, 3.0]

… и MotorCursor.distinct:

>>> @gen.engine
... def f():
...     print (yield motor.Op(db.test_collection.find().distinct, 'my_key'))
...     IOLoop.instance().stop()
... 
>>> f()
>>> IOLoop.instance().start()
[1.0, 2.0, 3.0]

Опять же, это просто удобные альтернативы использованию MotorDatabase.command:

>>> @gen.engine
... def f():
...     print (yield motor.Op(db.command,
...         SON([('distinct', 'test_collection'), ('key', 'my_key')])))
...     IOLoop.instance().stop()
... 
>>> f()
>>> IOLoop.instance().start()
{u'ok': 1.0,
 u'stats': {u'cursor': u'BasicCursor',
            u'n': 3,
            u'nscanned': 3,
            u'nscannedObjects': 3,
            u'timems': 0},
 u'values': [1.0, 2.0, 3.0]}

AsyncMongo

AsyncMongo — еще один драйвер для Tornado и MongoDB. Его интерфейс не так богат, как у Motor, поэтому я часто слышу такие вопросы, как «Поддерживает ли AsyncMongo distinct? Поддерживает ли он aggregate? Как насчет groupНа самом деле, это те вопросы, которые вызвали этот пост. И, конечно, ответ — да, AsyncMongo поддерживает все команды:

>>> from tornado.ioloop import IOLoop
>>> import asyncmongo
>>> db = asyncmongo.Client(
...     pool_id='mydb', host='127.0.0.1', port=27017,
...     maxcached=10, maxconnections=50, dbname='test')
>>> @gen.engine
... def f():
...     results = yield gen.Task(db.command,
...         SON([('distinct', 'test_collection'), ('key', 'my_key')]))
...     print results.args[0]
...     IOLoop.instance().stop()
... 
>>> f()
>>> IOLoop.instance().start()
{u'ok': 1.0,
 u'stats': {u'cursor': u'BasicCursor',
            u'n': 3,
            u'nscanned': 3,
            u'nscannedObjects': 3,
            u'timems': 0},
 u'values': [1.0, 2.0, 3.0]}

Исключения

Есть некоторые области, где драйверы действительно различаются, например, поддержка набора реплик или чтение настроек . Драйверы 10gen гораздо более последовательны, чем драйверы сторонних производителей. Но если основной операцией является команда, то все драйверы по сути одинаковы.

Итак, научитесь запускать команды

Поэтому в следующий раз, когда вы спросите: «Поддерживает ли драйвер X функцию Y», сначала проверьте, является ли команда Y командой, и найдите ее в справочнике команд . Скорее всего, он есть, и если это так, вы знаете, как его запустить.