From 7d03ca9e86e366c2c369101621d011eb6ee8b2c2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 24 Feb 2009 11:15:31 +0000 Subject: [PATCH] Fixed #10161 -- Modified evaluation of query expressions to allow for operators that take the form of functions. This is mostly for the benefit of Oracle, but it should prove useful later on. Thanks to Ian for the report and feedback on the fix. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9898 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/__init__.py | 9 +++++++++ django/db/backends/oracle/base.py | 10 ++++++++++ django/db/models/sql/expressions.py | 3 +-- tests/modeltests/expressions/models.py | 18 ++++++++++++++--- .../expressions_regress/models.py | 20 +++++++++++-------- 5 files changed, 47 insertions(+), 13 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 99f8f27ad9c..6b027de1931 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 d6bd3eab665..732f3ebba40 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 ef9fcb00c34..f011db21651 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 4043f5ec341..f29767a9d12 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 dfd0df050e4..7c9ca61ccc4 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 - -"""} +"""