From 3bc5f27cdacf9eda9d36b1ab1110b8bcf1f563ae Mon Sep 17 00:00:00 2001 From: M1ha Date: Sat, 8 Dec 2018 11:40:05 +0500 Subject: [PATCH 1/7] 1) Unified QuerySet filter and exclude methods, so both can load *q and **kwargs at the same time and accept prewhere flag 2) Added ability to add prewhere clause in QuerySet.filter() and QuerySet.exclude() methods 3) Added ability to check, if Q() object is empty (including bool check) 4) Refactored QuerySet.as_sql() method: + don't add GROUP BY and WHERE if it's not needed + ability to add PREWHERE condition + Common style of adding optional query parts --- src/infi/clickhouse_orm/query.py | 81 +++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index 1035dd4..4a77226 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -183,6 +183,14 @@ class Q(object): self._negate = False self._mode = self.AND_MODE + @property + def is_empty(self): + """ + Checks if there are any conditions in Q object + :return: Boolean + """ + return bool(self._fovs or self._l_child or self._r_child) + @classmethod def _construct_from(cls, l_child, r_child, mode): q = Q() @@ -203,7 +211,7 @@ class Q(object): 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( + sql = '({}) {} ({})'.format( self._l_child.to_sql(model_cls), self._mode, self._r_child.to_sql(model_cls)) else: return '1' @@ -222,6 +230,9 @@ class Q(object): q._negate = True return q + def __bool__(self): + return not self.is_empty + @six.python_2_unicode_compatible class QuerySet(object): @@ -239,7 +250,8 @@ class QuerySet(object): self._model_cls = model_cls self._database = database self._order_by = [] - self._q = [] + self._where_q = Q() + self._prewhere_q = Q() self._fields = model_cls.fields().keys() self._limits = None self._distinct = False @@ -303,14 +315,14 @@ class QuerySet(object): for field in self._order_by ]) - def conditions_as_sql(self): + def conditions_as_sql(self, q_object): """ - Returns the contents of the query's `WHERE` clause as a string. + Returns the contents of the query's `WHERE` or `PREWHERE` clause as a string. """ - if self._q: - return u' AND '.join([q.to_sql(self._model_cls) for q in self._q]) + if q_object: + return u' AND '.join([q.to_sql(self._model_cls) for q in q_object]) else: - return u'1' + return u'' def count(self): """ @@ -342,25 +354,40 @@ class QuerySet(object): qs._fields = field_names return qs - def filter(self, *q, **filter_fields): + def _filter_or_exclude(self, *q, **kwargs): + reverse = kwargs.pop('reverse', False) + prewhere = kwargs.pop('prewhere', False) + condition = copy(self._where_q) + qs = copy(self) + + if q: + condition &= q + + if kwargs: + condition &= Q(**kwargs) + + if reverse: + condition = ~condition + + if prewhere: + qs._prewhere_q = condition + else: + qs._where_q = condition + + return qs + + def filter(self, *q, **kwargs): """ Returns a copy of this queryset that includes only rows matching the conditions. Add q object to query if it specified. """ - qs = copy(self) - if q: - qs._q = list(self._q) + list(q) - else: - qs._q = list(self._q) + [Q(**filter_fields)] - return qs + return self._filter_or_exclude(*q, **kwargs) - def exclude(self, **filter_fields): + def exclude(self, *q, **kwargs): """ Returns a copy of this queryset that excludes all rows matching the conditions. """ - qs = copy(self) - qs._q = list(self._q) + [~Q(**filter_fields)] - return qs + return self._filter_or_exclude(*q, reverse=True, **kwargs) def paginate(self, page_num=1, page_size=100): """ @@ -476,20 +503,30 @@ class AggregateQuerySet(QuerySet): Returns the whole query as a SQL string. """ distinct = 'DISTINCT ' if self._distinct else '' - grouping = comma_join('`%s`' % field for field in self._grouping_fields) fields = comma_join(list(self._fields) + ['%s AS %s' % (v, k) for k, v in self._calculated_fields.items()]) + params = dict( distinct=distinct, - grouping=grouping or "''", fields=fields, table=self._model_cls.table_name(), - conds=self.conditions_as_sql() ) - sql = u'SELECT %(distinct)s%(fields)s\nFROM `%(table)s`\nWHERE %(conds)s\nGROUP BY %(grouping)s' % params + sql = u'SELECT %(distinct)s%(fields)s\nFROM `%(table)s`' % params + + if self._prewhere_q: + sql += '\nPREWHERE ' + self.conditions_as_sql(self._prewhere_q) + + if self._where_q: + sql += '\nWHERE ' + self.conditions_as_sql(self._where_q) + + if self._grouping_fields: + sql += '\nGROUP BY %s' % comma_join('`%s`' % field for field in self._grouping_fields) + if self._order_by: sql += '\nORDER BY ' + self.order_by_as_sql() + if self._limits: sql += '\nLIMIT %d, %d' % self._limits + return sql def __iter__(self): From 95055996adbfc8a2b3ba2b1c419d9c7f24d96bdc Mon Sep 17 00:00:00 2001 From: M1ha Date: Sat, 8 Dec 2018 11:57:08 +0500 Subject: [PATCH 2/7] 1) Further refactoring of as_sql(): merged very similar QuerySet and AggregateQuerySet methods 2) Fixed some bugs, caused by conditions_as_sql() parameters change --- src/infi/clickhouse_orm/query.py | 68 ++++++++++++++------------------ tests/test_querysets.py | 8 ++-- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index 4a77226..b6a8baf 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -252,6 +252,7 @@ class QuerySet(object): self._order_by = [] self._where_q = Q() self._prewhere_q = Q() + self._grouping_fields = [] self._fields = model_cls.fields().keys() self._limits = None self._distinct = False @@ -292,19 +293,34 @@ class QuerySet(object): qs._limits = (start, stop - start) return qs + def select_fields_as_sql(self): + return comma_join('`%s`' % field for field in self._fields) if self._fields else '*' + def as_sql(self): """ Returns the whole query as a SQL string. """ distinct = 'DISTINCT ' if self._distinct else '' - fields = '*' - if self._fields: - fields = comma_join('`%s`' % field for field in self._fields) - ordering = '\nORDER BY ' + self.order_by_as_sql() if self._order_by else '' - limit = '\nLIMIT %d, %d' % self._limits if self._limits else '' - params = (distinct, fields, self._model_cls.table_name(), - self.conditions_as_sql(), ordering, limit) - return u'SELECT %s%s\nFROM `%s`\nWHERE %s%s%s' % params + + params = (distinct, self.select_fields_as_sql(), self._model_cls.table_name()) + sql = u'SELECT %s%s\nFROM `%s`\n' % params + + if self._prewhere_q: + sql += '\nPREWHERE ' + self.conditions_as_sql(self._prewhere_q) + + if self._where_q: + sql += '\nWHERE ' + self.conditions_as_sql(self._where_q) + + if self._grouping_fields: + sql += '\nGROUP BY %s' % comma_join('`%s`' % field for field in self._grouping_fields) + + if self._order_by: + sql += '\nORDER BY ' + self.order_by_as_sql() + + if self._limits: + sql += '\nLIMIT %d, %d' % self._limits + + return def order_by_as_sql(self): """ @@ -333,8 +349,10 @@ class QuerySet(object): sql = u'SELECT count() FROM (%s)' % self.as_sql() raw = self._database.raw(sql) return int(raw) if raw else 0 + # Simple case - return self._database.count(self._model_cls, self.conditions_as_sql()) + conditions = self.conditions_as_sql(self._where_q & self._prewhere_q) + return self._database.count(self._model_cls, conditions) def order_by(self, *field_names): """ @@ -498,36 +516,8 @@ class AggregateQuerySet(QuerySet): """ raise NotImplementedError('Cannot re-aggregate an AggregateQuerySet') - def as_sql(self): - """ - Returns the whole query as a SQL string. - """ - distinct = 'DISTINCT ' if self._distinct else '' - fields = comma_join(list(self._fields) + ['%s AS %s' % (v, k) for k, v in self._calculated_fields.items()]) - - params = dict( - distinct=distinct, - fields=fields, - table=self._model_cls.table_name(), - ) - sql = u'SELECT %(distinct)s%(fields)s\nFROM `%(table)s`' % params - - if self._prewhere_q: - sql += '\nPREWHERE ' + self.conditions_as_sql(self._prewhere_q) - - if self._where_q: - sql += '\nWHERE ' + self.conditions_as_sql(self._where_q) - - if self._grouping_fields: - sql += '\nGROUP BY %s' % comma_join('`%s`' % field for field in self._grouping_fields) - - if self._order_by: - sql += '\nORDER BY ' + self.order_by_as_sql() - - if self._limits: - sql += '\nLIMIT %d, %d' % self._limits - - return sql + def select_fields_as_sql(self): + return comma_join(list(self._fields) + ['%s AS %s' % (v, k) for k, v in self._calculated_fields.items()]) def __iter__(self): return self._database.select(self.as_sql()) # using an ad-hoc model diff --git a/tests/test_querysets.py b/tests/test_querysets.py index a4fef14..9dd1ca4 100644 --- a/tests/test_querysets.py +++ b/tests/test_querysets.py @@ -369,13 +369,13 @@ class AggregateTestCase(TestCaseWithData): the__next__number = Int32Field() engine = Memory() qs = Mdl.objects_in(self.database).filter(the__number=1) - self.assertEqual(qs.conditions_as_sql(), 'the__number = 1') + self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__number = 1') qs = Mdl.objects_in(self.database).filter(the__number__gt=1) - self.assertEqual(qs.conditions_as_sql(), 'the__number > 1') + self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__number > 1') qs = Mdl.objects_in(self.database).filter(the__next__number=1) - self.assertEqual(qs.conditions_as_sql(), 'the__next__number = 1') + self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__next__number = 1') qs = Mdl.objects_in(self.database).filter(the__next__number__gt=1) - self.assertEqual(qs.conditions_as_sql(), 'the__next__number > 1') + self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__next__number > 1') Color = Enum('Color', u'red blue green yellow brown white black') From 12463c61b8369d484ef08e09b18803168eedf002 Mon Sep 17 00:00:00 2001 From: M1ha Date: Tue, 11 Dec 2018 17:25:54 +0500 Subject: [PATCH 3/7] 1) Fixed bugs 2) Edited the docs 3) Added test for prewhere --- docs/querysets.md | 14 +++++++++++--- src/infi/clickhouse_orm/query.py | 33 ++++++++++++++++---------------- tests/test_querysets.py | 9 ++++++++- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/docs/querysets.md b/docs/querysets.md index 260e66c..c4e84ea 100644 --- a/docs/querysets.md +++ b/docs/querysets.md @@ -17,21 +17,29 @@ The `filter` and `exclude` methods are used for filtering the matching instances >>> qs = Person.objects_in(database) >>> qs = qs.filter(first_name__startswith='V').exclude(birthday__lt='2000-01-01') - >>> qs.conditions_as_sql() + >>> qs.conditions_as_sql(qs._where) u"first_name LIKE 'V%' AND NOT (birthday < '2000-01-01')" It is possible to specify several fields to filter or exclude by: >>> qs = Person.objects_in(database).filter(last_name='Smith', height__gt=1.75) - >>> qs.conditions_as_sql() + >>> qs.conditions_as_sql(qs._where) 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() + >>> qs.conditions_as_sql(qs._where) u"((first_name = 'Ciaran' AND last_name = 'Carver') OR height <= 1.8) AND (NOT (first_name = 'David'))" +By default conditions from `filter` and `exclude` methods are add to `WHERE` clause. +For better aggregation performance you can add them to `PREWHERE` section using `prewhere=True` parameter + + >>> qs = Person.objects_in(database) + >>> qs = qs.filter(first_name__startswith='V', prewhere=True) + >>> qs.conditions_as_sql(qs._prewhere) + u"first_name LIKE 'V%'" + There are different operators that can be used, by passing `__=` (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 | diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index b6a8baf..c4839aa 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -189,14 +189,14 @@ class Q(object): Checks if there are any conditions in Q object :return: Boolean """ - return bool(self._fovs or self._l_child or self._r_child) + return not bool(self._fovs or self._l_child or self._r_child) @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 # AND/OR + q._mode = mode # AND/OR return q def _build_fov(self, key, value): @@ -209,14 +209,17 @@ class Q(object): def to_sql(self, model_cls): if self._fovs: sql = ' {} '.format(self._mode).join(fov.to_sql(model_cls) for fov in self._fovs) + elif 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)) + elif self._l_child or self._r_child: + # Return existing condition + sql = (self._l_child or self._r_child).to_sql(model_cls) 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' + sql = '1' + if self._negate: sql = 'NOT (%s)' % sql + return sql def __or__(self, other): @@ -303,7 +306,7 @@ class QuerySet(object): distinct = 'DISTINCT ' if self._distinct else '' params = (distinct, self.select_fields_as_sql(), self._model_cls.table_name()) - sql = u'SELECT %s%s\nFROM `%s`\n' % params + sql = u'SELECT %s%s\nFROM `%s`' % params if self._prewhere_q: sql += '\nPREWHERE ' + self.conditions_as_sql(self._prewhere_q) @@ -320,7 +323,7 @@ class QuerySet(object): if self._limits: sql += '\nLIMIT %d, %d' % self._limits - return + return sql def order_by_as_sql(self): """ @@ -335,10 +338,7 @@ class QuerySet(object): """ Returns the contents of the query's `WHERE` or `PREWHERE` clause as a string. """ - if q_object: - return u' AND '.join([q.to_sql(self._model_cls) for q in q_object]) - else: - return u'' + return q_object.to_sql(self._model_cls) def count(self): """ @@ -378,8 +378,8 @@ class QuerySet(object): condition = copy(self._where_q) qs = copy(self) - if q: - condition &= q + for q_obj in q: + condition &= q_obj if kwargs: condition &= Q(**kwargs) @@ -487,7 +487,8 @@ class AggregateQuerySet(QuerySet): self._grouping_fields = grouping_fields self._calculated_fields = calculated_fields self._order_by = list(base_qs._order_by) - self._q = list(base_qs._q) + self._where_q = base_qs._where_q + self._prewhere_q = base_qs._prewhere_q self._limits = base_qs._limits self._distinct = base_qs._distinct diff --git a/tests/test_querysets.py b/tests/test_querysets.py index 9dd1ca4..14db99c 100644 --- a/tests/test_querysets.py +++ b/tests/test_querysets.py @@ -11,7 +11,7 @@ from datetime import date, datetime try: Enum # exists in Python 3.4+ except NameError: - from enum import Enum # use the enum34 library instead + from enum import Enum # use the enum34 library instead class QuerySetTestCase(TestCaseWithData): @@ -29,6 +29,13 @@ class QuerySetTestCase(TestCaseWithData): self.assertEqual(count, expected_count) self.assertEqual(qs.count(), expected_count) + def test_prewhere(self): + # We can't distinguish prewhere and where results, it affects performance only. + # So let's control prewhere acts like where does + qs = Person.objects_in(self.database) + self.assertTrue(qs.filter(first_name='Connor', prewhere=True)) + self.assertFalse(qs.filter(first_name='Willy', prewhere=True)) + def test_no_filtering(self): qs = Person.objects_in(self.database) self._test_qs(qs, len(data)) From 0c92e2ac741608278d550af307903c0828c8cb8f Mon Sep 17 00:00:00 2001 From: M1ha Date: Tue, 11 Dec 2018 18:04:08 +0500 Subject: [PATCH 4/7] Simplified conditions, built by Q objects, if many conditions are joined in same mode --- src/infi/clickhouse_orm/query.py | 61 ++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index c4839aa..0052a0c 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import six import pytz -from copy import copy +from copy import copy, deepcopy from math import ceil from .utils import comma_join @@ -170,6 +170,11 @@ class FOV(object): def to_sql(self, model_cls): return self._operator.to_sql(model_cls, self._field_name, self._value) + def __deepcopy__(self, memodict={}): + res = copy(self) + res._value = deepcopy(self._value) + return res + class Q(object): @@ -178,8 +183,7 @@ class Q(object): 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._children = [] self._negate = False self._mode = self.AND_MODE @@ -189,14 +193,22 @@ class Q(object): Checks if there are any conditions in Q object :return: Boolean """ - return not bool(self._fovs or self._l_child or self._r_child) + return not bool(self._fovs or self._children) @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 # AND/OR + if mode == l_child._mode: + q = deepcopy(l_child) + q._children.append(deepcopy(r_child)) + elif mode == r_child._mode: + q = deepcopy(r_child) + q._children.append(deepcopy(l_child)) + else: + # Different modes + q = Q() + q._children = [l_child, r_child] + q._mode = mode # AND/OR + return q def _build_fov(self, key, value): @@ -207,15 +219,23 @@ class Q(object): return FOV(field_name, operator, value) def to_sql(self, model_cls): + condition_sql = [] + if self._fovs: - sql = ' {} '.format(self._mode).join(fov.to_sql(model_cls) for fov in self._fovs) - elif 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)) - elif self._l_child or self._r_child: - # Return existing condition - sql = (self._l_child or self._r_child).to_sql(model_cls) - else: + condition_sql.extend([fov.to_sql(model_cls) for fov in self._fovs]) + + if self._children: + condition_sql.extend([child.to_sql(model_cls) for child in self._children if child]) + + if not condition_sql: + # Empty Q() object returns everything sql = '1' + elif len(condition_sql) == 1: + # Skip not needed brackets over single condition + sql = condition_sql[0] + else: + # Each condition must be enclosed in brackets, or order of operations may be wrong + sql = '(%s)' % ') {} ('.format(self._mode).join(condition_sql) if self._negate: sql = 'NOT (%s)' % sql @@ -236,6 +256,17 @@ class Q(object): def __bool__(self): return not self.is_empty + def __deepcopy__(self, memodict={}): + q = Q() + q._fovs = [deepcopy(fov) for fov in self._fovs] + q._negate = self._negate + q._mode = self._mode + + if self._children: + q._children = [deepcopy(child) for child in self._children] + + return q + @six.python_2_unicode_compatible class QuerySet(object): From d1e61dc420af7ce7dd71e832693ba9453b68e624 Mon Sep 17 00:00:00 2001 From: M1ha Date: Mon, 17 Dec 2018 10:26:35 +0500 Subject: [PATCH 5/7] Fixed invalid condtion joins in _filter_or_exclude (multiple_exclude_test) --- src/infi/clickhouse_orm/query.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index 22c1484..b705320 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -411,9 +411,10 @@ class QuerySet(object): def _filter_or_exclude(self, *q, **kwargs): reverse = kwargs.pop('reverse', False) prewhere = kwargs.pop('prewhere', False) - condition = copy(self._where_q) + qs = copy(self) + condition = Q() for q_obj in q: condition &= q_obj @@ -423,6 +424,7 @@ class QuerySet(object): if reverse: condition = ~condition + condition = copy(self._prewhere_q if prewhere else self._where_q) & condition if prewhere: qs._prewhere_q = condition else: From 6902de3b94825569a6d2f11a79bbe7af10bce103 Mon Sep 17 00:00:00 2001 From: M1ha Date: Wed, 19 Dec 2018 10:06:57 +0500 Subject: [PATCH 6/7] Fix fo conditions_as_sql method by review --- src/infi/clickhouse_orm/query.py | 9 +++++---- tests/test_querysets.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/infi/clickhouse_orm/query.py b/src/infi/clickhouse_orm/query.py index b705320..9eee645 100644 --- a/src/infi/clickhouse_orm/query.py +++ b/src/infi/clickhouse_orm/query.py @@ -345,10 +345,10 @@ class QuerySet(object): sql = u'SELECT %s%s\nFROM `%s`%s' % params if self._prewhere_q: - sql += '\nPREWHERE ' + self.conditions_as_sql(self._prewhere_q) + sql += '\nPREWHERE ' + self.conditions_as_sql(prewhere=True) if self._where_q: - sql += '\nWHERE ' + self.conditions_as_sql(self._where_q) + sql += '\nWHERE ' + self.conditions_as_sql(prewhere=False) if self._grouping_fields: sql += '\nGROUP BY %s' % comma_join('`%s`' % field for field in self._grouping_fields) @@ -370,10 +370,11 @@ class QuerySet(object): for field in self._order_by ]) - def conditions_as_sql(self, q_object): + def conditions_as_sql(self, prewhere=False): """ Returns the contents of the query's `WHERE` or `PREWHERE` clause as a string. """ + q_object = self._prewhere_q if prewhere else self._where_q return q_object.to_sql(self._model_cls) def count(self): @@ -387,7 +388,7 @@ class QuerySet(object): return int(raw) if raw else 0 # Simple case - conditions = self.conditions_as_sql(self._where_q & self._prewhere_q) + conditions = (self._where_q & self._prewhere_q).to_sql(self._model_cls) return self._database.count(self._model_cls, conditions) def order_by(self, *field_names): diff --git a/tests/test_querysets.py b/tests/test_querysets.py index f5c20c4..298d859 100644 --- a/tests/test_querysets.py +++ b/tests/test_querysets.py @@ -417,13 +417,13 @@ class AggregateTestCase(TestCaseWithData): the__next__number = Int32Field() engine = Memory() qs = Mdl.objects_in(self.database).filter(the__number=1) - self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__number = 1') + self.assertEqual(qs.conditions_as_sql(), 'the__number = 1') qs = Mdl.objects_in(self.database).filter(the__number__gt=1) - self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__number > 1') + self.assertEqual(qs.conditions_as_sql(), 'the__number > 1') qs = Mdl.objects_in(self.database).filter(the__next__number=1) - self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__next__number = 1') + self.assertEqual(qs.conditions_as_sql(), 'the__next__number = 1') qs = Mdl.objects_in(self.database).filter(the__next__number__gt=1) - self.assertEqual(qs.conditions_as_sql(qs._where_q), 'the__next__number > 1') + self.assertEqual(qs.conditions_as_sql(), 'the__next__number > 1') Color = Enum('Color', u'red blue green yellow brown white black') From d11f8b3b76398cc1d844e282a463c5c821fc0df8 Mon Sep 17 00:00:00 2001 From: M1ha Date: Wed, 19 Dec 2018 10:10:03 +0500 Subject: [PATCH 7/7] Docs updated --- docs/class_reference.md | 8 ++++---- docs/querysets.md | 8 ++++---- docs/ref.md | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/class_reference.md b/docs/class_reference.md index c992d8e..6eb2353 100644 --- a/docs/class_reference.md +++ b/docs/class_reference.md @@ -835,10 +835,10 @@ is equivalent to: Returns the whole query as a SQL string. -#### conditions_as_sql() +#### conditions_as_sql(prewhere=True) -Returns the contents of the query's `WHERE` clause as a string. +Returns the contents of the query's `WHERE` or `PREWHERE` clause as a string. #### count() @@ -943,10 +943,10 @@ This method is not supported on `AggregateQuerySet`. Returns the whole query as a SQL string. -#### conditions_as_sql() +#### conditions_as_sql(prewhere=True) -Returns the contents of the query's `WHERE` clause as a string. +Returns the contents of the query's `WHERE` or `PREWHERE` clause as a string. #### count() diff --git a/docs/querysets.md b/docs/querysets.md index 965950e..2329000 100644 --- a/docs/querysets.md +++ b/docs/querysets.md @@ -17,19 +17,19 @@ The `filter` and `exclude` methods are used for filtering the matching instances >>> qs = Person.objects_in(database) >>> qs = qs.filter(first_name__startswith='V').exclude(birthday__lt='2000-01-01') - >>> qs.conditions_as_sql(qs._where) + >>> qs.conditions_as_sql() u"first_name LIKE 'V%' AND NOT (birthday < '2000-01-01')" It is possible to specify several fields to filter or exclude by: >>> qs = Person.objects_in(database).filter(last_name='Smith', height__gt=1.75) - >>> qs.conditions_as_sql(qs._where) + >>> 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(qs._where) + >>> qs.conditions_as_sql() u"((first_name = 'Ciaran' AND last_name = 'Carver') OR height <= 1.8) AND (NOT (first_name = 'David'))" By default conditions from `filter` and `exclude` methods are add to `WHERE` clause. @@ -37,7 +37,7 @@ For better aggregation performance you can add them to `PREWHERE` section using >>> qs = Person.objects_in(database) >>> qs = qs.filter(first_name__startswith='V', prewhere=True) - >>> qs.conditions_as_sql(qs._prewhere) + >>> qs.conditions_as_sql(prewhere=True) u"first_name LIKE 'V%'" There are different operators that can be used, by passing `__=` (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. diff --git a/docs/ref.md b/docs/ref.md index a298d04..e750d18 100644 --- a/docs/ref.md +++ b/docs/ref.md @@ -482,9 +482,9 @@ infi.clickhouse_orm.query #### QuerySet(model_cls, database) -#### conditions_as_sql() +#### conditions_as_sql(prewhere=True) -Return the contents of the queryset's WHERE clause. +Return the contents of the queryset's WHERE or `PREWHERE` clause. #### count()