Skip to content

Commit 6710a63

Browse files
tammoippendjc
authored andcommitted
Add methods for mango queries and indexes
`find` corresponds to endpoint `POST /{db}/_find` `explain` corresponds to endpoint `POST /{db}/_explain` `index`, `add_index` and `remove_index` correspond to `GET /{db}/_index`, `POST /{db}/_index` and `DELETE /{db}/_index/{designdoc}/json/{name}` These methods are only available for CouchDB server version >= 2.0.0. Added a note to `query`: temporary views are not available anymore for CouchDB server version >= 2.0.0. Also add tests for the new methods.
1 parent 48e38c6 commit 6710a63

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

couchdb/client.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,152 @@ def put_attachment(self, doc, content, filename=None, content_type=None):
775775
}, rev=doc['_rev'])
776776
doc['_rev'] = data['rev']
777777

778+
def find(self, mango_query, wrapper=None):
779+
"""Execute a mango find-query against the database.
780+
781+
Note: only available for CouchDB version >= 2.0.0
782+
783+
More information on the `mango_query` structure can be found here:
784+
http://docs.couchdb.org/en/master/api/database/find.html#find-selectors
785+
786+
>>> server = Server()
787+
>>> db = server.create('python-tests')
788+
>>> db['johndoe'] = dict(type='Person', name='John Doe')
789+
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
790+
>>> db['gotham'] = dict(type='City', name='Gotham City')
791+
>>> mango = {'selector': {'type': 'Person'},
792+
... 'fields': ['name'],
793+
... 'sort':[{'name': 'asc'}]}
794+
>>> for row in db.find(mango): # doctest: +SKIP
795+
... print(row['name']) # doctest: +SKIP
796+
John Doe
797+
Mary Jane
798+
>>> del server['python-tests']
799+
800+
:param mango_query: a dictionary describing criteria used to select
801+
documents
802+
:param wrapper: an optional callable that should be used to wrap the
803+
resulting documents
804+
:return: the query results as a list of `Document` (or whatever `wrapper` returns)
805+
"""
806+
status, headers, data = self.resource.post_json('_find', mango_query)
807+
return map(wrapper or Document, data.get('docs', []))
808+
809+
def explain(self, mango_query):
810+
"""Explain a mango find-query.
811+
812+
Note: only available for CouchDB version >= 2.0.0
813+
814+
More information on the `mango_query` structure can be found here:
815+
http://docs.couchdb.org/en/master/api/database/find.html#db-explain
816+
817+
>>> server = Server()
818+
>>> db = server.create('python-tests')
819+
>>> db['johndoe'] = dict(type='Person', name='John Doe')
820+
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
821+
>>> db['gotham'] = dict(type='City', name='Gotham City')
822+
>>> mango = {'selector': {'type': 'Person'}, 'fields': ['name']}
823+
>>> db.explain(mango) #doctest: +ELLIPSIS +SKIP
824+
{...}
825+
>>> del server['python-tests']
826+
827+
:param mango_query: a `dict` describing criteria used to select
828+
documents
829+
:return: the query results as a list of `Document` (or whatever
830+
`wrapper` returns)
831+
:rtype: `dict`
832+
"""
833+
_, _, data = self.resource.post_json('_explain', mango_query)
834+
return data
835+
836+
def index(self):
837+
"""Get all available indexes.
838+
839+
Note: Only available for CouchDB version >= 2.0.0 .
840+
841+
More information here:
842+
http://docs.couchdb.org/en/master/api/database/find.html#get--db-_index
843+
844+
>>> server = Server()
845+
>>> db = server.create('python-tests')
846+
>>> db.index() #doctest: +SKIP
847+
{'indexes': [{'ddoc': None,
848+
'def': {'fields': [{'_id': 'asc'}]},
849+
'name': '_all_docs',
850+
'type': 'special'}],
851+
'total_rows': 1}
852+
>>> del server['python-tests']
853+
854+
:return: `dict` containing the number of indexes (`total_rows`) and
855+
a description of each index (`indexes`)
856+
"""
857+
_, _, data = self.resource.get_json('_index')
858+
return data
859+
860+
def add_index(self, index, ddoc=None, name=None):
861+
"""Add an index to the database.
862+
863+
Note: Only available for CouchDB version >= 2.0.0 .
864+
865+
More information here:
866+
http://docs.couchdb.org/en/master/api/database/find.html#post--db-_index
867+
868+
>>> server = Server()
869+
>>> db = server.create('python-tests')
870+
>>> db['johndoe'] = dict(type='Person', name='John Doe')
871+
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
872+
>>> db['gotham'] = dict(type='City', name='Gotham City')
873+
>>> db.add_index({'fields': [{'type': 'asc'}]}, #doctest: +SKIP
874+
... ddoc='foo',
875+
... name='bar')
876+
{'id': '_design/foo', 'name': 'bar', 'result': 'created'}
877+
>>> del server['python-tests']
878+
879+
:param index: `dict` describing the index to create
880+
:param ddoc: (optional) name of the design document in which the index
881+
will be created
882+
:param name: (optional) name of the index
883+
:return: `dict` containing the `id`, the `name` and the `result` of
884+
creating the index
885+
"""
886+
assert isinstance(index, dict)
887+
query = {'index': index}
888+
if ddoc:
889+
query['ddoc'] = ddoc
890+
if name:
891+
query['name'] = name
892+
_, _, data = self.resource.post_json('_index', query)
893+
return data
894+
895+
def remove_index(self, ddoc, name):
896+
"""Remove an index from the database.
897+
898+
Note: Only available for CouchDB version >= 2.0.0 .
899+
900+
More information here:
901+
http://docs.couchdb.org/en/master/api/database/find.html#delete--db-_index-designdoc-json-name
902+
903+
>>> server = Server()
904+
>>> db = server.create('python-tests')
905+
>>> db['johndoe'] = dict(type='Person', name='John Doe')
906+
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
907+
>>> db['gotham'] = dict(type='City', name='Gotham City')
908+
>>> db.add_index({'fields': [{'type': 'asc'}]}, #doctest: +SKIP
909+
... ddoc='foo',
910+
... name='bar')
911+
{'id': '_design/foo', 'name': 'bar', 'result': 'created'}
912+
>>> db.remove_index('foo', 'bar') #doctest: +SKIP
913+
{'ok': True}
914+
>>> del server['python-tests']
915+
916+
:param ddoc: name of the design document containing the index
917+
:param name: name of the index that is to be removed
918+
:return: `dict` containing the `id`, the `name` and the `result` of
919+
creating the index
920+
"""
921+
_, _, data = self.resource.delete_json(['_index', ddoc, 'json', name])
922+
return data
923+
778924
def query(self, map_fun, reduce_fun=None, language='javascript',
779925
wrapper=None, **options):
780926
"""Execute an ad-hoc query (a "temp view") against the database.

couchdb/tests/client.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,76 @@ def test_query_multi_get(self):
374374
for idx, i in enumerate(range(1, 6, 2)):
375375
self.assertEqual(i, res[idx].key)
376376

377+
def test_find(self):
378+
if self.server.version_info()[0] < 2:
379+
return
380+
381+
docs = [
382+
dict(type='Person', name='John Doe'),
383+
dict(type='Person', name='Mary Jane'),
384+
dict(type='City', name='Gotham City')
385+
]
386+
self.db.update(docs)
387+
388+
res = list(self.db.find(
389+
{
390+
'selector': {
391+
'type': 'Person'
392+
},
393+
'fields': ['name'],
394+
'sort': [{'name': 'asc'}]
395+
}
396+
))
397+
self.assertEqual(2, len(res))
398+
expect = ['John Doe', 'Mary Jane']
399+
for i, doc in enumerate(res):
400+
self.assertEqual(set(['name']), doc.keys())
401+
self.assertEqual(expect[i], doc['name'])
402+
403+
def test_explain(self):
404+
if self.server.version_info()[0] < 2:
405+
return
406+
mango = {'selector': {'type': 'Person'}, 'fields': ['name']}
407+
res = self.db.explain(mango)
408+
409+
self.assertEqual(['name'], res['fields'])
410+
self.assertEqual({'type': {'$eq': 'Person'}}, res['selector'])
411+
self.assertEqual(0, res['skip'])
412+
self.assertEqual(self.db.name, res['dbname'])
413+
414+
def test_index(self):
415+
if self.server.version_info()[0] < 2:
416+
return
417+
418+
res = self.db.index()
419+
self.assertEqual(1, res['total_rows'])
420+
self.assertEqual(1, len(res['indexes']))
421+
self.assertEqual({'ddoc': None, 'def': {'fields': [{'_id': 'asc'}]},
422+
'name': '_all_docs', 'type': 'special'},
423+
res['indexes'][0])
424+
425+
def test_add_index(self):
426+
if self.server.version_info()[0] < 2:
427+
return
428+
429+
res = self.db.add_index({'fields': [{'type': 'asc'}]}, ddoc='foo', name='bar')
430+
self.assertEqual({'id': '_design/foo',
431+
'name': 'bar',
432+
'result': 'created'},
433+
res)
434+
res = self.db.index()
435+
self.assertEqual(2, res['total_rows'])
436+
437+
def test_remove_index(self):
438+
if self.server.version_info()[0] < 2:
439+
return
440+
441+
self.db.add_index({'fields': [{'type': 'asc'}]}, ddoc='foo', name='bar')
442+
res = self.db.remove_index('foo', 'bar')
443+
self.assertEqual({'ok': True}, res)
444+
res = self.db.index()
445+
self.assertEqual(1, res['total_rows'])
446+
377447
def test_bulk_update_conflict(self):
378448
docs = [
379449
dict(type='Person', name='John Doe'),

0 commit comments

Comments
 (0)