You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
7.8 KiB
233 lines
7.8 KiB
from .utils import DslBase, _make_dsl_class
|
|
from .function import SF, ScoreFunction
|
|
|
|
__all__ = [
|
|
'Q', 'Bool', 'Boosting', 'Common', 'ConstantScore', 'DisMax', 'Filtered',
|
|
'FunctionScore', 'Fuzzy', 'FuzzyLikeThis', 'FuzzyLikeThisField',
|
|
'GeoShape', 'HasChild', 'HasParent', 'Ids', 'Indices', 'Match', 'MatchAll',
|
|
'MatchPhrase', 'MatchPhrasePrefix', 'MoreLikeThis', 'MoreLikeThisField',
|
|
'MultiMatch', 'Nested', 'Prefix', 'Query', 'QueryString', 'Range',
|
|
'Regexp', 'SF', 'ScoreFunction', 'SimpleQueryString', 'SpanFirst',
|
|
'SpanMulti', 'SpanNear', 'SpanNot', 'SpanOr', 'SpanTerm', 'Template',
|
|
'Term', 'Terms', 'TopChildren', 'Wildcard'
|
|
]
|
|
|
|
|
|
def Q(name_or_query='match_all', **params):
|
|
# {"match": {"title": "python"}}
|
|
if isinstance(name_or_query, dict):
|
|
if params:
|
|
raise ValueError('Q() cannot accept parameters when passing in a dict.')
|
|
if len(name_or_query) != 1:
|
|
raise ValueError('Q() can only accept dict with a single query ({"match": {...}}). '
|
|
'Instead it got (%r)' % name_or_query)
|
|
name, params = name_or_query.copy().popitem()
|
|
return Query.get_dsl_class(name)(**params)
|
|
|
|
# MatchAll()
|
|
if isinstance(name_or_query, Query):
|
|
if params:
|
|
raise ValueError('Q() cannot accept parameters when passing in a Query object.')
|
|
return name_or_query
|
|
|
|
# s.query = Q('filtered', query=s.query)
|
|
if hasattr(name_or_query, '_proxied'):
|
|
return name_or_query._proxied
|
|
|
|
# "match", title="python"
|
|
return Query.get_dsl_class(name_or_query)(**params)
|
|
|
|
class Query(DslBase):
|
|
_type_name = 'query'
|
|
_type_shortcut = staticmethod(Q)
|
|
name = None
|
|
|
|
def __add__(self, other):
|
|
# make sure we give queries that know how to combine themselves
|
|
# preference
|
|
if hasattr(other, '__radd__'):
|
|
return other.__radd__(self)
|
|
return Bool(must=[self, other])
|
|
|
|
def __invert__(self):
|
|
return Bool(must_not=[self])
|
|
|
|
def __or__(self, other):
|
|
# make sure we give queries that know how to combine themselves
|
|
# preference
|
|
if hasattr(other, '__ror__'):
|
|
return other.__ror__(self)
|
|
return Bool(should=[self, other])
|
|
|
|
def __and__(self, other):
|
|
# make sure we give queries that know how to combine themselves
|
|
# preference
|
|
if hasattr(other, '__rand__'):
|
|
return other.__rand__(self)
|
|
return Bool(must=[self, other])
|
|
|
|
|
|
class MatchAll(Query):
|
|
name = 'match_all'
|
|
def __add__(self, other):
|
|
return other._clone()
|
|
__and__ = __rand__ = __radd__ = __add__
|
|
|
|
def __or__(self, other):
|
|
return self
|
|
__ror__ = __or__
|
|
EMPTY_QUERY = MatchAll()
|
|
|
|
class Bool(Query):
|
|
name = 'bool'
|
|
_param_defs = {
|
|
'must': {'type': 'query', 'multi': True},
|
|
'should': {'type': 'query', 'multi': True},
|
|
'must_not': {'type': 'query', 'multi': True},
|
|
'filter': {'type': 'query', 'multi': True},
|
|
}
|
|
|
|
def __add__(self, other):
|
|
q = self._clone()
|
|
if isinstance(other, Bool):
|
|
q.must += other.must
|
|
q.should += other.should
|
|
q.must_not += other.must_not
|
|
q.filter += other.filter
|
|
else:
|
|
q.must.append(other)
|
|
return q
|
|
__radd__ = __add__
|
|
|
|
def __or__(self, other):
|
|
for q in (self, other):
|
|
if isinstance(q, Bool) and len(q.should) == 1 and not any((q.must, q.must_not, q.filter)):
|
|
other = self if q is other else other
|
|
q = q._clone()
|
|
q.should.append(other)
|
|
return q
|
|
|
|
return Bool(should=[self, other])
|
|
__ror__ = __or__
|
|
|
|
def __invert__(self):
|
|
# special case for single negated query
|
|
if not (self.must or self.should or self.filter) and len(self.must_not) == 1:
|
|
return self.must_not[0]._clone()
|
|
|
|
# bol without should, just flip must and must_not
|
|
elif not self.should:
|
|
q = self._clone()
|
|
q.must, q.must_not = q.must_not, q.must
|
|
if q.filter:
|
|
q.filter = [Bool(must_not=q.filter)]
|
|
return q
|
|
|
|
# TODO: should -> must_not.append(Bool(should=self.should)) ??
|
|
# queries with should just invert normally
|
|
return super(Bool, self).__invert__()
|
|
|
|
def __and__(self, other):
|
|
q = self._clone()
|
|
if isinstance(other, Bool):
|
|
q.must += other.must
|
|
q.must_not += other.must_not
|
|
q.filter += other.filter
|
|
q.should = []
|
|
for qx in (self, other):
|
|
# TODO: percetages will fail here
|
|
min_should_match = getattr(qx, 'minimum_should_match', 0 if qx.must else 1)
|
|
# all subqueries are required
|
|
if len(qx.should) <= min_should_match:
|
|
q.must.extend(qx.should)
|
|
# not all of them are required, use it and remember min_should_match
|
|
elif not q.should:
|
|
q.minimum_should_match = min_should_match
|
|
q.should = qx.should
|
|
# not all are required, add a should list to the must with proper min_should_match
|
|
else:
|
|
q.must.append(Bool(should=qx.should, minimum_should_match=min_should_match))
|
|
else:
|
|
q.must.append(other)
|
|
return q
|
|
__rand__ = __and__
|
|
|
|
class FunctionScore(Query):
|
|
name = 'function_score'
|
|
_param_defs = {
|
|
'query': {'type': 'query'},
|
|
'filter': {'type': 'query'},
|
|
'functions': {'type': 'score_function', 'multi': True},
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
if 'functions' in kwargs:
|
|
pass
|
|
else:
|
|
fns = kwargs['functions'] = []
|
|
for name in ScoreFunction._classes:
|
|
if name in kwargs:
|
|
fns.append({name: kwargs.pop(name)})
|
|
super(FunctionScore, self).__init__(**kwargs)
|
|
|
|
QUERIES = (
|
|
# compound queries
|
|
('boosting', {'positive': {'type': 'query'}, 'negative': {'type': 'query'}}),
|
|
('constant_score', {'query': {'type': 'query'}, 'filter': {'type': 'query'}}),
|
|
('dis_max', {'queries': {'type': 'query', 'multi': True}}),
|
|
('filtered', {'query': {'type': 'query'}, 'filter': {'type': 'query'}}),
|
|
('indices', {'query': {'type': 'query'}, 'no_match_query': {'type': 'query'}}),
|
|
|
|
# relationship queries
|
|
('nested', {'query': {'type': 'query'}}),
|
|
('has_child', {'query': {'type': 'query'}}),
|
|
('has_parent', {'query': {'type': 'query'}}),
|
|
('top_children', {'query': {'type': 'query'}}),
|
|
|
|
# compount span queries
|
|
('span_first', {'match': {'type': 'query'}}),
|
|
('span_multi', {'match': {'type': 'query'}}),
|
|
('span_near', {'clauses': {'type': 'query', 'multi': True}}),
|
|
('span_not', {'exclude': {'type': 'query'}, 'include': {'type': 'query'}}),
|
|
('span_or', {'clauses': {'type': 'query', 'multi': True}}),
|
|
|
|
# core queries
|
|
('common', None),
|
|
('fuzzy', None),
|
|
('fuzzy_like_this', None),
|
|
('fuzzy_like_this_field', None),
|
|
('geo_bounding_box', None),
|
|
('geo_distance', None),
|
|
('geo_distance_range', None),
|
|
('geo_polygon', None),
|
|
('geo_shape', None),
|
|
('geohash_cell', None),
|
|
('ids', None),
|
|
('limit', None),
|
|
('match', None),
|
|
('match_phrase', None),
|
|
('match_phrase_prefix', None),
|
|
('exists', None),
|
|
('missing', None),
|
|
('more_like_this', None),
|
|
('more_like_this_field', None),
|
|
('multi_match', None),
|
|
('prefix', None),
|
|
('query_string', None),
|
|
('range', None),
|
|
('regexp', None),
|
|
('simple_query_string', None),
|
|
('span_term', None),
|
|
('template', None),
|
|
('term', None),
|
|
('terms', None),
|
|
('wildcard', None),
|
|
('script', None),
|
|
('type', None),
|
|
)
|
|
|
|
# generate the query classes dynamicaly
|
|
for qname, params_def in QUERIES:
|
|
qclass = _make_dsl_class(Query, qname, params_def)
|
|
globals()[qclass.__name__] = qclass
|
|
|
|
|