Explorar el Código

Fixed #19198 -- merged 4 different Oracle fixes

Anssi Kääriäinen hace 12 años
padre
commit
908efca817

+ 1 - 1
django/contrib/gis/tests/utils.py

@@ -26,7 +26,7 @@ mysql   = _default_db == 'mysql'
 spatialite = _default_db == 'spatialite'
 
 HAS_SPATIALREFSYS = True
-if oracle:
+if oracle and 'gis' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']:
     from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
 elif postgis:
     from django.contrib.gis.db.backends.postgis.models import SpatialRefSys

+ 4 - 0
django/db/backends/oracle/base.py

@@ -256,6 +256,10 @@ WHEN (new.%(col_name)s IS NULL)
         if not name.startswith('"') and not name.endswith('"'):
             name = '"%s"' % util.truncate_name(name.upper(),
                                                self.max_name_length())
+        # Oracle puts the query text into a (query % args) construct, so % signs
+        # in names need to be escaped. The '%%' will be collapsed back to '%' at
+        # that stage so we aren't really making the name longer here.
+        name = name.replace('%','%%')
         return name.upper()
 
     def random_function_sql(self):

+ 9 - 2
django/db/models/query.py

@@ -1418,8 +1418,15 @@ def get_cached_row(row, index_start, using,  klass_info, offset=0):
     fields = row[index_start : index_start + field_count]
     # If all the select_related columns are None, then the related
     # object must be non-existent - set the relation to None.
-    # Otherwise, construct the related object.
-    if fields == (None,) * field_count:
+    # Otherwise, construct the related object. Also, some backends treat ''
+    # and None equivalently for char fields, so we have to be prepared for
+    # '' values.
+    if connections[using].features.interprets_empty_strings_as_nulls:
+        vals = tuple([None if f == '' else f for f in fields])
+    else:
+        vals = fields
+
+    if vals == (None,) * field_count:
         obj = None
     else:
         if field_names:

+ 8 - 0
tests/regressiontests/backends/tests.py

@@ -25,6 +25,14 @@ from . import models
 
 class OracleChecks(unittest.TestCase):
 
+    @unittest.skipUnless(connection.vendor == 'oracle',
+                         "No need to check Oracle quote_name semantics")
+    def test_quote_name(self):
+        # Check that '%' chars are escaped for query execution.
+        name = '"SOME%NAME"'
+        quoted_name = connection.ops.quote_name(name)
+        self.assertEquals(quoted_name % (), name)
+
     @unittest.skipUnless(connection.vendor == 'oracle',
                          "No need to check Oracle cursor semantics")
     def test_dbms_session(self):

+ 7 - 4
tests/regressiontests/inspectdb/tests.py

@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 from django.core.management import call_command
+from django.db import connection
 from django.test import TestCase, skipUnlessDBFeature
 from django.utils.six import StringIO
 
@@ -60,14 +61,16 @@ class InspectDBTestCase(TestCase):
         self.assertIn("number_45extra = models.CharField", output)
 
     def test_special_column_name_introspection(self):
-        """Introspection of column names containing special characters,
-           unsuitable for Python identifiers
+        """
+        Introspection of column names containing special characters,
+        unsuitable for Python identifiers
         """
         out = StringIO()
         call_command('inspectdb', stdout=out)
         output = out.getvalue()
+        base_name = 'Field' if connection.vendor != 'oracle' else 'field'
         self.assertIn("field = models.IntegerField()", output)
-        self.assertIn("field_field = models.IntegerField(db_column='Field_')", output)
-        self.assertIn("field_field_0 = models.IntegerField(db_column='Field__')", output)
+        self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output)
+        self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output)
         self.assertIn("field_field_1 = models.IntegerField(db_column='__field')", output)
         self.assertIn("prc_x = models.IntegerField(db_column='prc(%) x')", output)

+ 13 - 2
tests/regressiontests/introspection/tests.py

@@ -4,10 +4,15 @@ from functools import update_wrapper
 
 from django.db import connection
 from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
-from django.utils import six
+from django.utils import six, unittest
 
 from .models import Reporter, Article
 
+if connection.vendor == 'oracle':
+    expectedFailureOnOracle = unittest.expectedFailure
+else:
+    expectedFailureOnOracle = lambda f: f
+
 #
 # The introspection module is optional, so methods tested here might raise
 # NotImplementedError. This is perfectly acceptable behavior for the backend
@@ -89,7 +94,13 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)
             [datatype(r[1], r) for r in desc],
             ['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']
         )
-        # Check also length of CharFields
+
+    # The following test fails on Oracle due to #17202 (can't correctly
+    # inspect the length of character columns).
+    @expectedFailureOnOracle
+    def test_get_table_description_col_lengths(self):
+        cursor = connection.cursor()
+        desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
         self.assertEqual(
             [r[3] for r in desc if datatype(r[1], r) == 'CharField'],
             [30, 30, 75]