From 915001ad0bd61600781d58ead798c8cae6e5cd25 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Thu, 26 Jun 2008 03:11:32 +0000 Subject: [PATCH] Fixed #7109 -- Quote certain values before passing them for substitution in Field.db_type(). This fixes a problem with using reserved words for field names in Oracle. Only affects Oracle at the moment, but the same changes could easily be used by other backends if they are required (requires changing creation.py, only). This commit also reverts [7501] so that if the fix doesn't work, it will show up in the tests (and if it does work, the tests will prevent us from breaking it again). git-svn-id: http://code.djangoproject.com/svn/django/trunk@7743 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/creation.py | 12 ++++++---- django/db/models/fields/__init__.py | 4 +++- django/utils/datastructures.py | 31 +++++++++++++++++++++++++ tests/regressiontests/queries/models.py | 6 ++--- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index f4ada55ac6..219686789f 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -5,9 +5,13 @@ from django.core import management # types, as strings. Column-type strings can contain format strings; they'll # be interpolated against the values of Field.__dict__ before being output. # If a column type is set to None, it won't be included in the output. +# +# Any format strings starting with "qn_" are quoted before being used in the +# output (the "qn_" prefix is stripped before the lookup is performed. + DATA_TYPES = { 'AutoField': 'NUMBER(11)', - 'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))', + 'BooleanField': 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))', 'CharField': 'NVARCHAR2(%(max_length)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', 'DateField': 'DATE', @@ -19,11 +23,11 @@ DATA_TYPES = { 'ImageField': 'NVARCHAR2(%(max_length)s)', 'IntegerField': 'NUMBER(11)', 'IPAddressField': 'VARCHAR2(15)', - 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', + 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))', 'OneToOneField': 'NUMBER(11)', 'PhoneNumberField': 'VARCHAR2(20)', - 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', - 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', + 'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', + 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', 'SlugField': 'NVARCHAR2(50)', 'SmallIntegerField': 'NUMBER(11)', 'TextField': 'NCLOB', diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 96bb466c7b..a893c25e63 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -16,6 +16,7 @@ from django.core import validators from django import oldforms from django import newforms as forms from django.core.exceptions import ObjectDoesNotExist +from django.utils.datastructures import DictWrapper from django.utils.functional import curry from django.utils.itercompat import tee from django.utils.text import capfirst @@ -161,8 +162,9 @@ class Field(object): # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. + data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") try: - return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__ + return get_creation_module().DATA_TYPES[self.get_internal_type()] % data except KeyError: return None diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4c278c0d8e..21a72f2d1e 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -343,3 +343,34 @@ class FileDict(dict): d = dict(self, content='') return dict.__repr__(d) return dict.__repr__(self) + +class DictWrapper(dict): + """ + Wraps accesses to a dictionary so that certain values (those starting with + the specified prefix) are passed through a function before being returned. + The prefix is removed before looking up the real value. + + Used by the SQL construction code to ensure that values are correctly + quoted before being used. + """ + def __init__(self, data, func, prefix): + super(DictWrapper, self).__init__(data) + self.func = func + self.prefix = prefix + + def __getitem__(self, key): + """ + Retrieves the real value after stripping the prefix string (if + present). If the prefix is present, pass the value through self.func + before returning, otherwise return the raw value. + """ + if key.startswith(self.prefix): + use_func = True + key = key[len(self.prefix):] + else: + use_func = False + value = super(DictWrapper, self).__getitem__(key) + if use_func: + return self.func(value) + return value + diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 2e84d89ea8..8059798f86 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -122,12 +122,12 @@ class LoopZ(models.Model): class CustomManager(models.Manager): def get_query_set(self): qs = super(CustomManager, self).get_query_set() - return qs.filter(is_public=True, tag__name='t1') + return qs.filter(public=True, tag__name='t1') class ManagedModel(models.Model): data = models.CharField(max_length=10) tag = models.ForeignKey(Tag) - is_public = models.BooleanField(default=True) + public = models.BooleanField(default=True) objects = CustomManager() normal_manager = models.Manager() @@ -730,7 +730,7 @@ More twisted cases, involving nested negations. Bug #7095 Updates that are filtered on the model being updated are somewhat tricky to get in MySQL. This exercises that case. ->>> mm = ManagedModel.objects.create(data='mm1', tag=t1, is_public=True) +>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True) >>> ManagedModel.objects.update(data='mm') A values() or values_list() query across joined models must use outer joins