From 6dbe56ed7855f34585884a2381fb1cec22ddc824 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sat, 24 Dec 2016 13:26:15 +0100 Subject: [PATCH] Fixed #27632 -- Unified query parameters by their values on Oracle. --- django/db/backends/oracle/base.py | 16 ++++++++++++++-- tests/aggregation_regress/tests.py | 13 +++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 2e6f02cc14..68936388d4 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -450,7 +450,7 @@ class FormatStylePlaceholderCursor(object): else: return [p.force_bytes for p in params] - def _fix_for_params(self, query, params): + def _fix_for_params(self, query, params, unify_by_values=False): # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it # it does want a trailing ';' but not a trailing '/'. However, these # characters must be included in the original query in case the query @@ -464,6 +464,18 @@ class FormatStylePlaceholderCursor(object): # Handle params as dict args = {k: ":%s" % k for k in params.keys()} query = convert_unicode(query % args, self.charset) + elif unify_by_values: + # Handle params as a dict with unified query parameters by their + # values. It can be used only in single query execute() because + # executemany() shares the formatted query with each of the params + # list. e.g. for input params = [0.75, 2, 0.75, 'sth', 0.75] + # params_dict = {0.75: ':arg0', 2: ':arg1', 'sth': ':arg2'} + # args = [':arg0', ':arg1', ':arg0', ':arg2', ':arg0'] + # params = {':arg0': 0.75, ':arg1': 2, ':arg2': 'sth'} + params_dict = {param: ':arg%d' % i for i, param in enumerate(set(params))} + args = [params_dict[param] for param in params] + params = dict(zip(params_dict.values(), params_dict.keys())) + query = convert_unicode(query % tuple(args), self.charset) else: # Handle params as sequence args = [(':arg%d' % i) for i in range(len(params))] @@ -471,7 +483,7 @@ class FormatStylePlaceholderCursor(object): return query, self._format_params(params) def execute(self, query, params=None): - query, params = self._fix_for_params(query, params) + query, params = self._fix_for_params(query, params, unify_by_values=True) self._guess_input_sizes([params]) try: return self.cursor.execute(query, self._param_generator(params)) diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py index 59a40c7176..0229020234 100644 --- a/tests/aggregation_regress/tests.py +++ b/tests/aggregation_regress/tests.py @@ -107,6 +107,19 @@ class AggregationTests(TestCase): for attr, value in six.iteritems(kwargs): self.assertEqual(getattr(obj, attr), value) + def test_annotation_with_value(self): + values = Book.objects.filter( + name='Practical Django Projects', + ).annotate( + discount_price=F('price') * 2, + ).values( + 'discount_price', + ).annotate(sum_discount=Sum('discount_price')) + self.assertSequenceEqual( + values, + [{'discount_price': Decimal('59.38'), 'sum_discount': Decimal('59.38')}] + ) + def test_aggregates_in_where_clause(self): """ Regression test for #12822: DatabaseError: aggregates not allowed in