mirror of https://github.com/django/django.git
Fixed #28046 -- Added the db_tablespace parameter to class-based indexes.
Thanks Markus Holtermann and Tim Graham for reviews.
This commit is contained in:
parent
617505ca89
commit
3297dede7f
|
@ -879,16 +879,15 @@ class BaseDatabaseSchemaEditor:
|
||||||
index_name = "D%s" % index_name[:-1]
|
index_name = "D%s" % index_name[:-1]
|
||||||
return index_name
|
return index_name
|
||||||
|
|
||||||
def _get_index_tablespace_sql(self, model, fields):
|
def _get_index_tablespace_sql(self, model, fields, db_tablespace=None):
|
||||||
if len(fields) == 1 and fields[0].db_tablespace:
|
if db_tablespace is None:
|
||||||
tablespace_sql = self.connection.ops.tablespace_sql(fields[0].db_tablespace)
|
if len(fields) == 1 and fields[0].db_tablespace:
|
||||||
elif model._meta.db_tablespace:
|
db_tablespace = fields[0].db_tablespace
|
||||||
tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace)
|
elif model._meta.db_tablespace:
|
||||||
else:
|
db_tablespace = model._meta.db_tablespace
|
||||||
tablespace_sql = ""
|
if db_tablespace is not None:
|
||||||
if tablespace_sql:
|
return ' ' + self.connection.ops.tablespace_sql(db_tablespace)
|
||||||
tablespace_sql = " " + tablespace_sql
|
return ''
|
||||||
return tablespace_sql
|
|
||||||
|
|
||||||
def _create_index_sql(self, model, fields, suffix="", sql=None):
|
def _create_index_sql(self, model, fields, suffix="", sql=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Index:
|
||||||
# cross-database compatibility with Oracle)
|
# cross-database compatibility with Oracle)
|
||||||
max_name_length = 30
|
max_name_length = 30
|
||||||
|
|
||||||
def __init__(self, *, fields=[], name=None):
|
def __init__(self, *, fields=[], name=None, db_tablespace=None):
|
||||||
if not isinstance(fields, list):
|
if not isinstance(fields, list):
|
||||||
raise ValueError('Index.fields must be a list.')
|
raise ValueError('Index.fields must be a list.')
|
||||||
if not fields:
|
if not fields:
|
||||||
|
@ -29,6 +29,7 @@ class Index:
|
||||||
errors.append('Index names cannot be longer than %s characters.' % self.max_name_length)
|
errors.append('Index names cannot be longer than %s characters.' % self.max_name_length)
|
||||||
if errors:
|
if errors:
|
||||||
raise ValueError(errors)
|
raise ValueError(errors)
|
||||||
|
self.db_tablespace = db_tablespace
|
||||||
|
|
||||||
def check_name(self):
|
def check_name(self):
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -44,7 +45,7 @@ class Index:
|
||||||
|
|
||||||
def get_sql_create_template_values(self, model, schema_editor, using):
|
def get_sql_create_template_values(self, model, schema_editor, using):
|
||||||
fields = [model._meta.get_field(field_name) for field_name, order in self.fields_orders]
|
fields = [model._meta.get_field(field_name) for field_name, order in self.fields_orders]
|
||||||
tablespace_sql = schema_editor._get_index_tablespace_sql(model, fields)
|
tablespace_sql = schema_editor._get_index_tablespace_sql(model, fields, self.db_tablespace)
|
||||||
quote_name = schema_editor.quote_name
|
quote_name = schema_editor.quote_name
|
||||||
columns = [
|
columns = [
|
||||||
('%s %s' % (quote_name(field.column), order)).strip()
|
('%s %s' % (quote_name(field.column), order)).strip()
|
||||||
|
@ -73,7 +74,10 @@ class Index:
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
||||||
path = path.replace('django.db.models.indexes', 'django.db.models')
|
path = path.replace('django.db.models.indexes', 'django.db.models')
|
||||||
return (path, (), {'fields': self.fields, 'name': self.name})
|
kwargs = {'fields': self.fields, 'name': self.name}
|
||||||
|
if self.db_tablespace is not None:
|
||||||
|
kwargs['db_tablespace'] = self.db_tablespace
|
||||||
|
return (path, (), kwargs)
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""Create a copy of this Index."""
|
"""Create a copy of this Index."""
|
||||||
|
|
|
@ -23,7 +23,7 @@ options`_.
|
||||||
``Index`` options
|
``Index`` options
|
||||||
=================
|
=================
|
||||||
|
|
||||||
.. class:: Index(fields=[], name=None)
|
.. class:: Index(fields=[], name=None, db_tablespace=None)
|
||||||
|
|
||||||
Creates an index (B-Tree) in the database.
|
Creates an index (B-Tree) in the database.
|
||||||
|
|
||||||
|
@ -57,6 +57,23 @@ The name of the index. If ``name`` isn't provided Django will auto-generate a
|
||||||
name. For compatibility with different databases, index names cannot be longer
|
name. For compatibility with different databases, index names cannot be longer
|
||||||
than 30 characters and shouldn't start with a number (0-9) or underscore (_).
|
than 30 characters and shouldn't start with a number (0-9) or underscore (_).
|
||||||
|
|
||||||
|
``db_tablespace``
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. attribute:: Index.db_tablespace
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
The name of the :doc:`database tablespace </topics/db/tablespaces>` to use for
|
||||||
|
this index. For single field indexes, if ``db_tablespace`` isn't provided, the
|
||||||
|
index is created in the ``db_tablespace`` of the field.
|
||||||
|
|
||||||
|
If :attr:`.Field.db_tablespace` isn't specified (or if the index uses multiple
|
||||||
|
fields), the index is created in tablespace specified in the
|
||||||
|
:attr:`~django.db.models.Options.db_tablespace` option inside the model's
|
||||||
|
``class Meta``. If neither of those tablespaces are set, the index is created
|
||||||
|
in the same tablespace as the table.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
For a list of PostgreSQL-specific indexes, see
|
For a list of PostgreSQL-specific indexes, see
|
||||||
|
|
|
@ -245,6 +245,9 @@ Models
|
||||||
function to truncate :class:`~django.db.models.DateField` and
|
function to truncate :class:`~django.db.models.DateField` and
|
||||||
:class:`~django.db.models.DateTimeField` to the first day of a quarter.
|
:class:`~django.db.models.DateTimeField` to the first day of a quarter.
|
||||||
|
|
||||||
|
* Added the :attr:`~django.db.models.Index.db_tablespace` parameter to
|
||||||
|
class-based indexes.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,12 @@ cannot control.
|
||||||
Declaring tablespaces for indexes
|
Declaring tablespaces for indexes
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
You can pass the :attr:`~django.db.models.Field.db_tablespace` option to a
|
You can pass the :attr:`~django.db.models.Index.db_tablespace` option to an
|
||||||
``Field`` constructor to specify an alternate tablespace for the ``Field``’s
|
``Index`` constructor to specify the name of a tablespace to use for the index.
|
||||||
column index. If no index would be created for the column, the option is
|
For single field indexes, you can pass the
|
||||||
ignored.
|
:attr:`~django.db.models.Field.db_tablespace` option to a ``Field`` constructor
|
||||||
|
to specify an alternate tablespace for the field's column index. If the column
|
||||||
|
doesn't have an index, the option is ignored.
|
||||||
|
|
||||||
You can use the :setting:`DEFAULT_INDEX_TABLESPACE` setting to specify
|
You can use the :setting:`DEFAULT_INDEX_TABLESPACE` setting to specify
|
||||||
a default value for :attr:`~django.db.models.Field.db_tablespace`.
|
a default value for :attr:`~django.db.models.Field.db_tablespace`.
|
||||||
|
@ -49,17 +51,20 @@ An example
|
||||||
class TablespaceExample(models.Model):
|
class TablespaceExample(models.Model):
|
||||||
name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
|
name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
|
||||||
data = models.CharField(max_length=255, db_index=True)
|
data = models.CharField(max_length=255, db_index=True)
|
||||||
|
shortcut = models.CharField(max_length=7)
|
||||||
edges = models.ManyToManyField(to="self", db_tablespace="indexes")
|
edges = models.ManyToManyField(to="self", db_tablespace="indexes")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_tablespace = "tables"
|
db_tablespace = "tables"
|
||||||
|
indexes = [models.Index(fields=['shortcut'], db_tablespace='other_indexes')]
|
||||||
|
|
||||||
In this example, the tables generated by the ``TablespaceExample`` model (i.e.
|
In this example, the tables generated by the ``TablespaceExample`` model (i.e.
|
||||||
the model table and the many-to-many table) would be stored in the ``tables``
|
the model table and the many-to-many table) would be stored in the ``tables``
|
||||||
tablespace. The index for the name field and the indexes on the many-to-many
|
tablespace. The index for the name field and the indexes on the many-to-many
|
||||||
table would be stored in the ``indexes`` tablespace. The ``data`` field would
|
table would be stored in the ``indexes`` tablespace. The ``data`` field would
|
||||||
also generate an index, but no tablespace for it is specified, so it would be
|
also generate an index, but no tablespace for it is specified, so it would be
|
||||||
stored in the model tablespace ``tables`` by default.
|
stored in the model tablespace ``tables`` by default. The index for the
|
||||||
|
``shortcut`` field would be stored in the ``other_indexes`` tablespace.
|
||||||
|
|
||||||
Database support
|
Database support
|
||||||
================
|
================
|
||||||
|
|
|
@ -5,6 +5,8 @@ class Book(models.Model):
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
author = models.CharField(max_length=50)
|
author = models.CharField(max_length=50)
|
||||||
pages = models.IntegerField(db_column='page_count')
|
pages = models.IntegerField(db_column='page_count')
|
||||||
|
shortcut = models.CharField(max_length=50, db_tablespace='idx_tbls')
|
||||||
|
isbn = models.CharField(max_length=50, db_tablespace='idx_tbls')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [models.indexes.Index(fields=['title'])]
|
indexes = [models.indexes.Index(fields=['title'])]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.db import models
|
from django.conf import settings
|
||||||
from django.test import SimpleTestCase
|
from django.db import connection, models
|
||||||
|
from django.test import SimpleTestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import Book, ChildModel1, ChildModel2
|
from .models import Book, ChildModel1, ChildModel2
|
||||||
|
|
||||||
|
@ -70,12 +71,15 @@ class IndexesTests(SimpleTestCase):
|
||||||
long_field_index.set_name_with_model(Book)
|
long_field_index.set_name_with_model(Book)
|
||||||
|
|
||||||
def test_deconstruction(self):
|
def test_deconstruction(self):
|
||||||
index = models.Index(fields=['title'])
|
index = models.Index(fields=['title'], db_tablespace='idx_tbls')
|
||||||
index.set_name_with_model(Book)
|
index.set_name_with_model(Book)
|
||||||
path, args, kwargs = index.deconstruct()
|
path, args, kwargs = index.deconstruct()
|
||||||
self.assertEqual(path, 'django.db.models.Index')
|
self.assertEqual(path, 'django.db.models.Index')
|
||||||
self.assertEqual(args, ())
|
self.assertEqual(args, ())
|
||||||
self.assertEqual(kwargs, {'fields': ['title'], 'name': 'model_index_title_196f42_idx'})
|
self.assertEqual(
|
||||||
|
kwargs,
|
||||||
|
{'fields': ['title'], 'name': 'model_index_title_196f42_idx', 'db_tablespace': 'idx_tbls'}
|
||||||
|
)
|
||||||
|
|
||||||
def test_clone(self):
|
def test_clone(self):
|
||||||
index = models.Index(fields=['title'])
|
index = models.Index(fields=['title'])
|
||||||
|
@ -92,3 +96,39 @@ class IndexesTests(SimpleTestCase):
|
||||||
self.assertEqual(index_names, ['model_index_name_440998_idx'])
|
self.assertEqual(index_names, ['model_index_name_440998_idx'])
|
||||||
index_names = [index.name for index in ChildModel2._meta.indexes]
|
index_names = [index.name for index in ChildModel2._meta.indexes]
|
||||||
self.assertEqual(index_names, ['model_index_name_b6c374_idx'])
|
self.assertEqual(index_names, ['model_index_name_b6c374_idx'])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_tablespaces')
|
||||||
|
def test_db_tablespace(self):
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
# Index with db_tablespace attribute.
|
||||||
|
for fields in [
|
||||||
|
# Field with db_tablespace specified on model.
|
||||||
|
['shortcut'],
|
||||||
|
# Field without db_tablespace specified on model.
|
||||||
|
['author'],
|
||||||
|
# Multi-column with db_tablespaces specified on model.
|
||||||
|
['shortcut', 'isbn'],
|
||||||
|
# Multi-column without db_tablespace specified on model.
|
||||||
|
['title', 'author'],
|
||||||
|
]:
|
||||||
|
with self.subTest(fields=fields):
|
||||||
|
index = models.Index(fields=fields, db_tablespace='idx_tbls2')
|
||||||
|
self.assertIn('"idx_tbls2"', index.create_sql(Book, editor).lower())
|
||||||
|
# Indexes without db_tablespace attribute.
|
||||||
|
for fields in [['author'], ['shortcut', 'isbn'], ['title', 'author']]:
|
||||||
|
with self.subTest(fields=fields):
|
||||||
|
index = models.Index(fields=fields)
|
||||||
|
# The DEFAULT_INDEX_TABLESPACE setting can't be tested
|
||||||
|
# because it's evaluated when the model class is defined.
|
||||||
|
# As a consequence, @override_settings doesn't work.
|
||||||
|
if settings.DEFAULT_INDEX_TABLESPACE:
|
||||||
|
self.assertIn(
|
||||||
|
'"%s"' % settings.DEFAULT_INDEX_TABLESPACE,
|
||||||
|
index.create_sql(Book, editor).lower()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertNotIn('TABLESPACE', index.create_sql(Book, editor))
|
||||||
|
# Field with db_tablespace specified on the model and an index
|
||||||
|
# without db_tablespace.
|
||||||
|
index = models.Index(fields=['shortcut'])
|
||||||
|
self.assertIn('"idx_tbls"', index.create_sql(Book, editor).lower())
|
||||||
|
|
Loading…
Reference in New Issue