Skip to content

method for mango query #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions couchdb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import socket

from couchdb import http, json, util
from couchdb.query_utils import Q

__all__ = ['Server', 'Database', 'Document', 'ViewResults', 'Row']
__docformat__ = 'restructuredtext en'
Expand Down Expand Up @@ -811,6 +812,58 @@ def find(self, mango_query, wrapper=None):
status, headers, data = self.resource.post_json('_find', mango_query)
return map(wrapper or Document, data.get('docs', []))

def filter(self, *args, **kwargs):
"""Execute a mango find-query against the database.

Note: only available for CouchDB version >= 2.0.0

More information on the `mango_query` structure can be found here:
http://docs.couchdb.org/en/master/api/database/find.html#find-selectors

>>> server = Server()
>>> db = server.create('python-tests')
>>> db['johndoe'] = dict(type='Person', name='John Doe', hobby=dict(main='read', second='game'), feats=[dict(feat1=1, feat2=2), dict(feat1=10, feat2=20)])
>>> db['maryjane'] = dict(type='Person', name='Mary Jane', hobby=dict(main='read'), feats=[dict(feat1=3, feat2=4), dict(feat1=30, feat2=40)])
>>> db['sillybilly'] = dict(type='Person', name='Silly Billy', hobby=dict(main='read', second='game'), feats=[dict(feat1=5, feat2=6), dict(feat1=50, feat2=60)])
>>> db['billysilly'] = dict(type='Person', name='Billy Silly', hobby=dict(main='swim', second='horse riding'), feats=[dict(feat1=1, feat2=2), dict(feat1=30, feat2=60)])
>>> for row in db.filter(name__eq='John Doe'): # doctest: +SKIP
... print(row['name']) # doctest: +SKIP
John Doe
>>> for row in db.filter(name__regex='(*UTF)(?i)illy'): # doctest: +SKIP
... print(row['name']) # doctest: +SKIP
Billy Silly
Silly Billy
>>> from couchdb.query_utils import Q
>>> for row in db.filter(Q(name__regex='(*UTF)(?i)silly') & Q(hobby__main__eq='read')): # doctest: +SKIP
... print(row['name']) # doctest: +SKIP
Silly Billy
>>> for row in db.filter(feats_L_elemMatch__feat2__in = [20, 4]): # doctest: +SKIP
... print(row['name']) # doctest: +SKIP
John Doe
Mary Jane
>>> del server['python-tests']

:param args: Q objects, parse into a query - Request JSON Object selector:
http://docs.couchdb.org/en/master/api/database/find.html#post--db-_find
:param kwargs: Request JSON Object:
http://docs.couchdb.org/en/master/api/database/find.html#post--db-_find
:return: the query results as a list of `Document`
"""
limit = kwargs.pop('limit', 30)
fields = kwargs.pop('fields', [])
sort = kwargs.pop('sort', [])
if args:
selector = args[0].P
elif kwargs:
selector = Q(**kwargs).P
else:
raise Exception('Need query, for example: name__eq = "Name" or used Q for complex conditions')
query = dict(selector=selector, limit=limit, fields=fields, sort=sort)
data = self.find(mango_query=query)
return data



def explain(self, mango_query):
"""Explain a mango find-query.

Expand Down
70 changes: 70 additions & 0 deletions couchdb/query_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
try:
from functools import reduce
except ImportError:
pass

'''
maybe overwrite
'''

class Q:
"""
Q-object need for creating mango_query in filter function
"""
OR = '$or'
AND = '$and'

def __init__(self, **kwarg):
"""
:param kwarg: field__subfield1__...__mango_operator = value
separate by double underscore - '__'
"""
self.field_cond = list(kwarg.keys())[0]
self.val = list(kwarg.values())[0]

@property
def P(self):
"""
P - parser
:return: dict for query
"""
field_cond_list = self.field_cond.split('__')
field = field_cond_list[:-1]
for i, subf in enumerate(field):
if '_L_' in subf:
subf_list = subf.split('_L_')
field.insert(i + 1, '$' + subf_list[1])
field[i] = subf_list[0]

cond = '$' + field_cond_list[-1]
parsed_field_cond = reduce(lambda x, y: {y: x},
reversed(field + [cond, self.val]))
return parsed_field_cond

def _combine(self, other, cond):
"""
:param other: Q or _FQ objects
:param cond: or, and
:return: _FQ object
"""
query = dict()
query[cond] = [self.P, other.P]
return _FQ(query)

def __or__(self, other):
return self._combine(other, self.OR)

def __and__(self, other):
return self._combine(other, self.AND)


class _FQ(Q):
'''
subobject need for _combine method in Q
'''
def __init__(self, val):
self.val = val

@property
def P(self):
return self.val