diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 99f8f27ad9..6b027de193 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -408,6 +408,15 @@ class BaseDatabaseOperations(object): """ pass + def combine_expression(self, connector, sub_expressions): + """Combine a list of subexpressions into a single expression, using + the provided connecting operator. This is required because operators + can vary between backends (e.g., Oracle with %% and &) and between + subexpression types (e.g., date expressions) + """ + conn = ' %s ' % connector + return conn.join(sub_expressions) + class BaseDatabaseIntrospection(object): """ This class encapsulates all backend-specific introspection utilities diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index d6bd3eab66..732f3ebba4 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -221,6 +221,16 @@ WHEN (new.%(col_name)s IS NULL) second = '%s-12-31' return [first % value, second % value] + def combine_expression(self, connector, sub_expressions): + "Oracle requires special cases for %% and & operators in query expressions" + if connector == '%%': + return 'MOD(%s)' % ','.join(sub_expressions) + elif connector == '&': + return 'BITAND(%s)' % ','.join(sub_expressions) + elif connector == '|': + raise NotImplementedError("Bit-wise or is not supported in Oracle.") + return super(DatabaseOperations, self).combine_expression(connector, sub_expressions) + class DatabaseWrapper(BaseDatabaseWrapper): diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py index ef9fcb00c3..f011db2165 100644 --- a/django/db/models/sql/expressions.py +++ b/django/db/models/sql/expressions.py @@ -74,9 +74,8 @@ class SQLEvaluator(object): if sql: expressions.append(format % sql) expression_params.extend(params) - conn = ' %s ' % node.connector - return conn.join(expressions), expression_params + return connection.ops.combine_expression(node.connector, expressions), expression_params def evaluate_leaf(self, node, qn): if not qn: diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py index 4043f5ec34..f29767a9d1 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/modeltests/expressions/models.py @@ -37,14 +37,26 @@ __test__ = {'API_TESTS': """ >>> Company(name='Test GmbH', num_employees=32, num_chairs=1, ... ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save() +>>> company_query = Company.objects.values('name','num_employees','num_chairs').order_by('name','num_employees','num_chairs') + # We can filter for companies where the number of employees is greater than the # number of chairs. +>>> company_query.filter(num_employees__gt=F('num_chairs')) +[{'num_chairs': 5, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 1, 'name': u'Test GmbH', 'num_employees': 32}] ->>> Company.objects.filter(num_employees__gt=F('num_chairs')) -[, ] +# We can set one field to have the value of another field +# Make sure we have enough chairs +>>> _ = company_query.update(num_chairs=F('num_employees')) +>>> company_query +[{'num_chairs': 2300, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 3, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 32, 'name': u'Test GmbH', 'num_employees': 32}] + +# We can perform arithmetic operations in expressions +# Make sure we have 2 spare chairs +>>> _ =company_query.update(num_chairs=F('num_employees')+2) +>>> company_query +[{'num_chairs': 2302, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 5, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 34, 'name': u'Test GmbH', 'num_employees': 32}] # The relation of a foreign key can become copied over to an other foreign key. - >>> Company.objects.update(point_of_contact=F('ceo')) 3 diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/regressiontests/expressions_regress/models.py index dfd0df050e..7c9ca61ccc 100644 --- a/tests/regressiontests/expressions_regress/models.py +++ b/tests/regressiontests/expressions_regress/models.py @@ -1,7 +1,7 @@ """ Spanning tests for all the operations that F() expressions can perform. """ - +from django.conf import settings from django.db import models # @@ -87,11 +87,6 @@ Complex expressions of different connection types are possible. >>> Number.objects.get(pk=n.pk) # LH Bitwise ands on integers ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') | 48) ->>> Number.objects.get(pk=n.pk) # LH Bitwise or on integers - - # Right hand operators >>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) @@ -123,11 +118,20 @@ Complex expressions of different connection types are possible. >>> _ = Number.objects.filter(pk=n.pk).update(integer=15 & F('integer')) >>> Number.objects.get(pk=n.pk) # RH Bitwise ands on integers +"""} + +# Oracle doesn't support the Bitwise OR operator. +if settings.DATABASE_ENGINE != 'oracle': + __test__['API_TESTS'] += """ + +>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) +>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') | 48) +>>> Number.objects.get(pk=n.pk) # LH Bitwise or on integers + >>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) >>> _ = Number.objects.filter(pk=n.pk).update(integer=15 | F('integer')) >>> Number.objects.get(pk=n.pk) # RH Bitwise or on integers - -"""} +"""