mirror of
https://github.com/Infinidat/infi.clickhouse_orm.git
synced 2024-11-10 19:36:33 +03:00
Merge branch 'desile-develop' into develop
This commit is contained in:
commit
51ba2d9d21
|
@ -26,6 +26,12 @@ It is possible to specify several fields to filter or exclude by:
|
|||
>>> qs.conditions_as_sql()
|
||||
u"last_name = 'Smith' AND height > 1.75"
|
||||
|
||||
For filters with compound conditions you can use `Q` objects inside `filter` with overloaded operators `&` (AND), `|` (OR) and `~` (NOT):
|
||||
|
||||
>>> qs = Person.objects_in(database).filter((Q(first_name='Ciaran', last_name='Carver') | Q(height_lte=1.8)) & ~Q(first_name='David'))
|
||||
>>> qs.conditions_as_sql()
|
||||
u"((first_name = 'Ciaran' AND last_name = 'Carver') OR height <= 1.8) AND (NOT (first_name = 'David'))"
|
||||
|
||||
There are different operators that can be used, by passing `<fieldname>__<operator>=<value>` (two underscores separate the field name from the operator). In case no operator is given, `eq` is used by default. Below are all the supported operators.
|
||||
|
||||
| Operator | Equivalent SQL | Comments |
|
||||
|
@ -36,6 +42,7 @@ There are different operators that can be used, by passing `<fieldname>__<operat
|
|||
| `gte` | `field >= value` | |
|
||||
| `lt` | `field < value` | |
|
||||
| `lte` | `field <= value` | |
|
||||
| `between` | `field BETWEEN value1 AND value2` | |
|
||||
| `in` | `field IN (values)` | See below |
|
||||
| `not_in` | `field NOT IN (values)` | See below |
|
||||
| `contains` | `field LIKE '%value%'` | For string fields only |
|
||||
|
|
|
@ -103,6 +103,29 @@ class NotOperator(Operator):
|
|||
return 'NOT (%s)' % self._base_operator.to_sql(model_cls, field_name, value)
|
||||
|
||||
|
||||
class BetweenOperator(Operator):
|
||||
"""
|
||||
An operator that implements BETWEEN.
|
||||
Accepts list or tuple of two elements and generates sql condition:
|
||||
- 'BETWEEN value[0] AND value[1]' if value[0] and value[1] are not None and not empty
|
||||
Then imitations of BETWEEN, where one of two limits is missing
|
||||
- '>= value[0]' if value[1] is None or empty
|
||||
- '<= value[1]' if value[0] is None or empty
|
||||
"""
|
||||
|
||||
def to_sql(self, model_cls, field_name, value):
|
||||
field = getattr(model_cls, field_name)
|
||||
value0 = field.to_db_string(
|
||||
field.to_python(value[0], pytz.utc)) if value[0] is not None or len(str(value[0])) > 0 else None
|
||||
value1 = field.to_db_string(
|
||||
field.to_python(value[1], pytz.utc)) if value[1] is not None or len(str(value[1])) > 0 else None
|
||||
if value0 and value1:
|
||||
return '%s BETWEEN %s and %s' % (field_name, value0, value1)
|
||||
if value0 and not value1:
|
||||
return ' '.join([field_name, '>=', value0])
|
||||
if value1 and not value0:
|
||||
return ' '.join([field_name, '<=', value1])
|
||||
|
||||
# Define the set of builtin operators
|
||||
|
||||
_operators = {}
|
||||
|
@ -116,6 +139,7 @@ register_operator('gt', SimpleOperator('>'))
|
|||
register_operator('gte', SimpleOperator('>='))
|
||||
register_operator('lt', SimpleOperator('<'))
|
||||
register_operator('lte', SimpleOperator('<='))
|
||||
register_operator('between', BetweenOperator())
|
||||
register_operator('in', InOperator())
|
||||
register_operator('not_in', NotOperator(InOperator()))
|
||||
register_operator('contains', LikeOperator('%{}%'))
|
||||
|
@ -143,9 +167,23 @@ class FOV(object):
|
|||
|
||||
class Q(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._fovs = [self._build_fov(k, v) for k, v in six.iteritems(kwargs)]
|
||||
AND_MODE = 'AND'
|
||||
OR_MODE = 'OR'
|
||||
|
||||
def __init__(self, **filter_fields):
|
||||
self._fovs = [self._build_fov(k, v) for k, v in six.iteritems(filter_fields)]
|
||||
self._l_child = None
|
||||
self._r_child = None
|
||||
self._negate = False
|
||||
self._mode = self.AND_MODE
|
||||
|
||||
@classmethod
|
||||
def _construct_from(cls, l_child, r_child, mode):
|
||||
q = Q()
|
||||
q._l_child = l_child
|
||||
q._r_child = r_child
|
||||
q._mode = mode
|
||||
return q
|
||||
|
||||
def _build_fov(self, key, value):
|
||||
if '__' in key:
|
||||
|
@ -155,13 +193,24 @@ class Q(object):
|
|||
return FOV(field_name, operator, value)
|
||||
|
||||
def to_sql(self, model_cls):
|
||||
if not self._fovs:
|
||||
return '1'
|
||||
sql = ' AND '.join(fov.to_sql(model_cls) for fov in self._fovs)
|
||||
if self._fovs:
|
||||
sql = ' {} '.format(self._mode).join(fov.to_sql(model_cls) for fov in self._fovs)
|
||||
else:
|
||||
if self._l_child and self._r_child:
|
||||
sql = '({}) {} ({})'.format(
|
||||
self._l_child.to_sql(model_cls), self._mode, self._r_child.to_sql(model_cls))
|
||||
else:
|
||||
return '1'
|
||||
if self._negate:
|
||||
sql = 'NOT (%s)' % sql
|
||||
return sql
|
||||
|
||||
def __or__(self, other):
|
||||
return Q._construct_from(self, other, self.OR_MODE)
|
||||
|
||||
def __and__(self, other):
|
||||
return Q._construct_from(self, other, self.AND_MODE)
|
||||
|
||||
def __invert__(self):
|
||||
q = copy(self)
|
||||
q._negate = True
|
||||
|
@ -286,20 +335,24 @@ class QuerySet(object):
|
|||
qs._fields = field_names
|
||||
return qs
|
||||
|
||||
def filter(self, **kwargs):
|
||||
def filter(self, *q, **filter_fields):
|
||||
"""
|
||||
Returns a copy of this queryset that includes only rows matching the conditions.
|
||||
Add q object to query if it specified.
|
||||
"""
|
||||
qs = copy(self)
|
||||
qs._q = list(self._q) + [Q(**kwargs)]
|
||||
if q:
|
||||
qs._q = list(self._q) + list(q)
|
||||
else:
|
||||
qs._q = list(self._q) + [Q(**filter_fields)]
|
||||
return qs
|
||||
|
||||
def exclude(self, **kwargs):
|
||||
def exclude(self, **filter_fields):
|
||||
"""
|
||||
Returns a copy of this queryset that excludes all rows matching the conditions.
|
||||
"""
|
||||
qs = copy(self)
|
||||
qs._q = list(self._q) + [~Q(**kwargs)]
|
||||
qs._q = list(self._q) + [~Q(**filter_fields)]
|
||||
return qs
|
||||
|
||||
def paginate(self, page_num=1, page_size=100):
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function
|
|||
import unittest
|
||||
|
||||
from infi.clickhouse_orm.database import Database
|
||||
from infi.clickhouse_orm.query import Q
|
||||
from .base_test_with_data import *
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
|
@ -59,6 +60,15 @@ class QuerySetTestCase(TestCaseWithData):
|
|||
self._test_qs(qs.filter(first_name__iendswith='ia'), 3) # case insensitive
|
||||
self._test_qs(qs.filter(first_name__iendswith=''), 100) # empty suffix
|
||||
|
||||
def test_filter_with_q_objects(self):
|
||||
qs = Person.objects_in(self.database)
|
||||
self._test_qs(qs.filter(Q(first_name='Ciaran')), 2)
|
||||
self._test_qs(qs.filter(Q(first_name='Ciaran') | Q(first_name='Chelsea')), 3)
|
||||
self._test_qs(qs.filter(Q(first_name__in=['Warren', 'Whilemina', 'Whitney']) & Q(height__gte=1.7)), 3)
|
||||
self._test_qs(qs.filter((Q(first_name__in=['Warren', 'Whilemina', 'Whitney']) & Q(height__gte=1.7) |
|
||||
(Q(first_name__in=['Victoria', 'Victor', 'Venus']) & Q(height__lt=1.7)))), 4)
|
||||
self._test_qs(qs.filter(Q(first_name='Elton') & ~Q(last_name='Smith')), 1)
|
||||
|
||||
def test_filter_unicode_string(self):
|
||||
self.database.insert([
|
||||
Person(first_name=u'דונלד', last_name=u'דאק')
|
||||
|
|
Loading…
Reference in New Issue
Block a user