summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2017-05-23 17:02:40 +0200
committerGitHub <noreply@github.com>2017-05-23 17:02:40 +0200
commit538bf43458a147b7edeb7118c9f325c3f59ff6fb (patch)
treea1724f358e8d8829c0c68b1e8bba6882d108fc53
parentb3eb6eaf1a197ff155faf333871da032c77ba855 (diff)
Fixed #27859 -- Ignored db_index for TextField/BinaryField on Oracle and MySQL.
Thanks Zubair Alam for the initial patch and Tim Graham for the review.
-rw-r--r--django/db/backends/base/features.py3
-rw-r--r--django/db/backends/mysql/base.py8
-rw-r--r--django/db/backends/mysql/features.py1
-rw-r--r--django/db/backends/mysql/schema.py20
-rw-r--r--django/db/backends/mysql/validation.py15
-rw-r--r--django/db/backends/oracle/base.py5
-rw-r--r--django/db/backends/oracle/features.py1
-rw-r--r--django/db/backends/oracle/schema.py7
-rw-r--r--django/db/backends/oracle/validation.py22
-rw-r--r--docs/ref/checks.txt2
-rw-r--r--tests/invalid_models_tests/test_ordinary_fields.py25
-rw-r--r--tests/schema/models.py7
-rw-r--r--tests/schema/tests.py17
13 files changed, 114 insertions, 19 deletions
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 09112cebb6..4b38a2d6b1 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -227,6 +227,9 @@ class BaseDatabaseFeatures:
supports_select_difference = True
supports_slicing_ordering_in_compound = False
+ # Does the backend support indexing a TextField?
+ supports_index_on_text_field = True
+
def __init__(self, connection):
self.connection = connection
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index f32dee09a8..35cc7dfc04 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -143,6 +143,14 @@ class DatabaseWrapper(BaseDatabaseWrapper):
else:
return self._data_types
+ # For these columns, MySQL doesn't:
+ # - accept default values and implicitly treats these columns as nullable
+ # - support a database index
+ _limited_data_types = (
+ 'tinyblob', 'blob', 'mediumblob', 'longblob', 'tinytext', 'text',
+ 'mediumtext', 'longtext', 'json',
+ )
+
operators = {
'exact': '= %s',
'iexact': 'LIKE %s',
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index ef02e28bb8..585ea073be 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -31,6 +31,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_select_intersection = False
supports_select_difference = False
supports_slicing_ordering_in_compound = True
+ supports_index_on_text_field = False
@cached_property
def _mysql_storage_engine(self):
diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py
index 455fd1b0e3..9cae8b79b5 100644
--- a/django/db/backends/mysql/schema.py
+++ b/django/db/backends/mysql/schema.py
@@ -29,20 +29,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
import MySQLdb.converters
return MySQLdb.escape(value, MySQLdb.converters.conversions)
- def skip_default(self, field):
- """
- MySQL doesn't accept default values for some data types and implicitly
- treats these columns as nullable.
- """
+ def _is_limited_data_type(self, field):
db_type = field.db_type(self.connection)
- return (
- db_type is not None and
- db_type.lower() in {
- 'tinyblob', 'blob', 'mediumblob', 'longblob',
- 'tinytext', 'text', 'mediumtext', 'longtext',
- 'json',
- }
- )
+ return db_type is not None and db_type.lower() in self.connection._limited_data_types
+
+ def skip_default(self, field):
+ return self._is_limited_data_type(field)
def add_field(self, model, field):
super().add_field(model, field)
@@ -69,6 +61,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
field.get_internal_type() == 'ForeignKey' and
field.db_constraint):
return False
+ if self._is_limited_data_type(field):
+ return False
return create_index
def _delete_composed_index(self, model, fields, *args):
diff --git a/django/db/backends/mysql/validation.py b/django/db/backends/mysql/validation.py
index b4ae265b03..5cd9a42856 100644
--- a/django/db/backends/mysql/validation.py
+++ b/django/db/backends/mysql/validation.py
@@ -31,6 +31,7 @@ class DatabaseValidation(BaseDatabaseValidation):
MySQL has the following field length restriction:
No character (varchar) fields can have a length exceeding 255
characters if they have a unique index on them.
+ MySQL doesn't support a database index on some data types.
"""
errors = []
if (field_type.startswith('varchar') and field.unique and
@@ -42,4 +43,18 @@ class DatabaseValidation(BaseDatabaseValidation):
id='mysql.E001',
)
)
+
+ if field.db_index and field_type.lower() in self.connection._limited_data_types:
+ errors.append(
+ checks.Warning(
+ 'MySQL does not support a database index on %s columns.'
+ % field_type,
+ hint=(
+ "An index won't be created. Silence this warning if "
+ "you don't care about it."
+ ),
+ obj=field,
+ id='fields.W162',
+ )
+ )
return errors
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index fb49fc5ffb..e2d8f351b0 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -55,6 +55,7 @@ from .introspection import DatabaseIntrospection # NOQA isort:skip
from .operations import DatabaseOperations # NOQA isort:skip
from .schema import DatabaseSchemaEditor # NOQA isort:skip
from .utils import Oracle_datetime # NOQA isort:skip
+from .validation import DatabaseValidation # NOQA isort:skip
class _UninitializedOperatorsDescriptor:
@@ -115,6 +116,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'PositiveSmallIntegerField': '%(qn_column)s >= 0',
}
+ # Oracle doesn't support a database index on these columns.
+ _limited_data_types = ('clob', 'nclob', 'blob')
+
operators = _UninitializedOperatorsDescriptor()
_standard_operators = {
@@ -174,6 +178,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
features_class = DatabaseFeatures
introspection_class = DatabaseIntrospection
ops_class = DatabaseOperations
+ validation_class = DatabaseValidation
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index 3e72a5c29d..af95136fa7 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -35,3 +35,4 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# Oracle doesn't ignore quoted identifiers case but the current backend
# does by uppercasing all identifiers.
ignores_table_name_case = True
+ supports_index_on_text_field = False
diff --git a/django/db/backends/oracle/schema.py b/django/db/backends/oracle/schema.py
index fe85edab17..5b1ae0adcb 100644
--- a/django/db/backends/oracle/schema.py
+++ b/django/db/backends/oracle/schema.py
@@ -119,3 +119,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
def prepare_default(self, value):
return self.quote_value(value)
+
+ def _field_should_be_indexed(self, model, field):
+ create_index = super()._field_should_be_indexed(model, field)
+ db_type = field.db_type(self.connection)
+ if db_type is not None and db_type.lower() in self.connection._limited_data_types:
+ return False
+ return create_index
diff --git a/django/db/backends/oracle/validation.py b/django/db/backends/oracle/validation.py
new file mode 100644
index 0000000000..e5a35fd3ca
--- /dev/null
+++ b/django/db/backends/oracle/validation.py
@@ -0,0 +1,22 @@
+from django.core import checks
+from django.db.backends.base.validation import BaseDatabaseValidation
+
+
+class DatabaseValidation(BaseDatabaseValidation):
+ def check_field_type(self, field, field_type):
+ """Oracle doesn't support a database index on some data types."""
+ errors = []
+ if field.db_index and field_type.lower() in self.connection._limited_data_types:
+ errors.append(
+ checks.Warning(
+ 'Oracle does not support a database index on %s columns.'
+ % field_type,
+ hint=(
+ "An index won't be created. Silence this warning if "
+ "you don't care about it."
+ ),
+ obj=field,
+ id='fields.W162',
+ )
+ )
+ return errors
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 570fa7393c..64fd6b68aa 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -172,6 +172,8 @@ Model fields
* **fields.E160**: The options ``auto_now``, ``auto_now_add``, and ``default``
are mutually exclusive. Only one of these options may be present.
* **fields.W161**: Fixed default value provided.
+* **fields.W162**: ``<database>`` does not support a database index on
+ ``<field data type>`` columns.
* **fields.E900**: ``IPAddressField`` has been removed except for support in
historical migrations.
* **fields.W900**: ``IPAddressField`` has been deprecated. Support for it
diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py
index 90f4b31902..cad375aae5 100644
--- a/tests/invalid_models_tests/test_ordinary_fields.py
+++ b/tests/invalid_models_tests/test_ordinary_fields.py
@@ -2,7 +2,7 @@ import unittest
from django.core.checks import Error, Warning as DjangoWarning
from django.db import connection, models
-from django.test import SimpleTestCase, TestCase
+from django.test import SimpleTestCase, TestCase, skipIfDBFeature
from django.test.utils import isolate_apps, override_settings
from django.utils.timezone import now
@@ -646,3 +646,26 @@ class TimeFieldTests(TestCase):
@override_settings(USE_TZ=True)
def test_fix_default_value_tz(self):
self.test_fix_default_value()
+
+
+@isolate_apps('invalid_models_tests')
+class TextFieldTests(TestCase):
+
+ @skipIfDBFeature('supports_index_on_text_field')
+ def test_max_length_warning(self):
+ class Model(models.Model):
+ value = models.TextField(db_index=True)
+ field = Model._meta.get_field('value')
+ field_type = field.db_type(connection)
+ self.assertEqual(field.check(), [
+ DjangoWarning(
+ '%s does not support a database index on %s columns.'
+ % (connection.display_name, field_type),
+ hint=(
+ "An index won't be created. Silence this warning if you "
+ "don't care about it."
+ ),
+ obj=field,
+ id='fields.W162',
+ )
+ ])
diff --git a/tests/schema/models.py b/tests/schema/models.py
index 0c73760d96..0e04dbab87 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -17,6 +17,13 @@ class Author(models.Model):
apps = new_apps
+class AuthorTextFieldWithIndex(models.Model):
+ text_field = models.TextField(db_index=True)
+
+ class Meta:
+ apps = new_apps
+
+
class AuthorWithDefaultHeight(models.Model):
name = models.CharField(max_length=255)
height = models.PositiveIntegerField(null=True, blank=True, default=42)
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 17a4d7c715..ef4b192a36 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -29,11 +29,11 @@ from .fields import (
CustomManyToManyField, InheritedManyToManyField, MediumBlobField,
)
from .models import (
- Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName,
- AuthorWithIndexedName, Book, BookForeignObj, BookWeak, BookWithLongName,
- BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK, Node, Note,
- NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing,
- UniqueTest, new_apps,
+ Author, AuthorTextFieldWithIndex, AuthorWithDefaultHeight,
+ AuthorWithEvenLongerName, AuthorWithIndexedName, Book, BookForeignObj,
+ BookWeak, BookWithLongName, BookWithO2O, BookWithoutAuthor, BookWithSlug,
+ IntegerPK, Node, Note, NoteRename, Tag, TagIndexed, TagM2MTest,
+ TagUniqueRename, Thing, UniqueTest, new_apps,
)
@@ -1749,6 +1749,13 @@ class SchemaTests(TransactionTestCase):
self.get_indexes(Book._meta.db_table),
)
+ def test_text_field_with_db_index(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(AuthorTextFieldWithIndex)
+ # The text_field index is present if the database supports it.
+ assertion = self.assertIn if connection.features.supports_index_on_text_field else self.assertNotIn
+ assertion('text_field', self.get_indexes(AuthorTextFieldWithIndex._meta.db_table))
+
def test_primary_key(self):
"""
Tests altering of the primary key