Fixed #12308 -- Added tablespace support to the PostgreSQL backend.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16987 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Aymeric Augustin 2011-10-14 21:49:43 +00:00
parent 69e1e6187a
commit 246580573d
18 changed files with 251 additions and 73 deletions

View File

@ -365,6 +365,10 @@ class BaseDatabaseFeatures(object):
# date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas # date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas
supports_mixed_date_datetime_comparisons = True supports_mixed_date_datetime_comparisons = True
# Does the backend support tablespaces? Default to False because it isn't
# in the SQL standard.
supports_tablespaces = False
# Features that need to be confirmed at runtime # Features that need to be confirmed at runtime
# Cache whether the confirmation has been performed. # Cache whether the confirmation has been performed.
_confirmed = False _confirmed = False
@ -696,8 +700,12 @@ class BaseDatabaseOperations(object):
def tablespace_sql(self, tablespace, inline=False): def tablespace_sql(self, tablespace, inline=False):
""" """
Returns the SQL that will be appended to tables or rows to define Returns the SQL that will be used in a query to define the tablespace.
a tablespace. Returns '' if the backend doesn't use tablespaces.
Returns '' if the backend doesn't support tablespaces.
If inline is True, the SQL is appended to a row; otherwise it's appended
to the entire CREATE TABLE or CREATE INDEX statement.
""" """
return '' return ''

View File

@ -57,7 +57,9 @@ class BaseDatabaseCreation(object):
if tablespace and f.unique: if tablespace and f.unique:
# We must specify the index tablespace inline, because we # We must specify the index tablespace inline, because we
# won't be generating a CREATE INDEX statement for this field. # won't be generating a CREATE INDEX statement for this field.
field_output.append(self.connection.ops.tablespace_sql(tablespace, inline=True)) tablespace_sql = self.connection.ops.tablespace_sql(tablespace, inline=True)
if tablespace_sql:
field_output.append(tablespace_sql)
if f.rel: if f.rel:
ref_output, pending = self.sql_for_inline_foreign_key_references(f, known_models, style) ref_output, pending = self.sql_for_inline_foreign_key_references(f, known_models, style)
if pending: if pending:
@ -74,7 +76,9 @@ class BaseDatabaseCreation(object):
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
full_statement.append(')') full_statement.append(')')
if opts.db_tablespace: if opts.db_tablespace:
full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace)) tablespace_sql = self.connection.ops.tablespace_sql(opts.db_tablespace)
if tablespace_sql:
full_statement.append(tablespace_sql)
full_statement.append(';') full_statement.append(';')
final_output.append('\n'.join(full_statement)) final_output.append('\n'.join(full_statement))
@ -149,11 +153,9 @@ class BaseDatabaseCreation(object):
qn = self.connection.ops.quote_name qn = self.connection.ops.quote_name
tablespace = f.db_tablespace or model._meta.db_tablespace tablespace = f.db_tablespace or model._meta.db_tablespace
if tablespace: if tablespace:
sql = self.connection.ops.tablespace_sql(tablespace) tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
if sql: if tablespace_sql:
tablespace_sql = ' ' + sql tablespace_sql = ' ' + tablespace_sql
else:
tablespace_sql = ''
else: else:
tablespace_sql = '' tablespace_sql = ''
i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column)) i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))

View File

@ -79,6 +79,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_defer_constraint_checks = True can_defer_constraint_checks = True
ignores_nulls_in_unique_constraints = False ignores_nulls_in_unique_constraints = False
has_bulk_insert = True has_bulk_insert = True
supports_tablespaces = True
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.oracle.compiler" compiler_module = "django.db.backends.oracle.compiler"
@ -326,8 +327,10 @@ WHEN (new.%(col_name)s IS NULL)
return '' return ''
def tablespace_sql(self, tablespace, inline=False): def tablespace_sql(self, tablespace, inline=False):
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), if inline:
self.quote_name(tablespace)) return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace)
else:
return "TABLESPACE %s" % self.quote_name(tablespace)
def value_to_db_datetime(self, value): def value_to_db_datetime(self, value):
# Oracle doesn't support tz-aware datetimes # Oracle doesn't support tz-aware datetimes

View File

@ -75,7 +75,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_select_for_update = True has_select_for_update = True
has_select_for_update_nowait = True has_select_for_update_nowait = True
has_bulk_insert = True has_bulk_insert = True
supports_tablespaces = True
class DatabaseWrapper(BaseDatabaseWrapper): class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'postgresql' vendor = 'postgresql'

View File

@ -44,11 +44,9 @@ class DatabaseCreation(BaseDatabaseCreation):
db_table = model._meta.db_table db_table = model._meta.db_table
tablespace = f.db_tablespace or model._meta.db_tablespace tablespace = f.db_tablespace or model._meta.db_tablespace
if tablespace: if tablespace:
sql = self.connection.ops.tablespace_sql(tablespace) tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
if sql: if tablespace_sql:
tablespace_sql = ' ' + sql tablespace_sql = ' ' + tablespace_sql
else:
tablespace_sql = ''
else: else:
tablespace_sql = '' tablespace_sql = ''

View File

@ -99,6 +99,12 @@ class DatabaseOperations(BaseDatabaseOperations):
else: else:
return [] return []
def tablespace_sql(self, tablespace, inline=False):
if inline:
return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace)
else:
return "TABLESPACE %s" % self.quote_name(tablespace)
def sequence_reset_sql(self, style, model_list): def sequence_reset_sql(self, style, model_list):
from django.db import models from django.db import models
output = [] output = []

View File

@ -1078,6 +1078,7 @@ def create_many_to_many_intermediary_model(field, klass):
'managed': managed, 'managed': managed,
'auto_created': klass, 'auto_created': klass,
'app_label': klass._meta.app_label, 'app_label': klass._meta.app_label,
'db_tablespace': klass._meta.db_tablespace,
'unique_together': (from_, to), 'unique_together': (from_, to),
'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to}, 'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to}, 'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
@ -1086,8 +1087,8 @@ def create_many_to_many_intermediary_model(field, klass):
return type(name, (models.Model,), { return type(name, (models.Model,), {
'Meta': meta, 'Meta': meta,
'__module__': klass.__module__, '__module__': klass.__module__,
from_: models.ForeignKey(klass, related_name='%s+' % name), from_: models.ForeignKey(klass, related_name='%s+' % name, db_tablespace=field.db_tablespace),
to: models.ForeignKey(to_model, related_name='%s+' % name) to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace)
}) })
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):

View File

@ -219,9 +219,9 @@ parameters:
* :attr:`~django.db.models.Field.choices` * :attr:`~django.db.models.Field.choices`
* :attr:`~django.db.models.Field.help_text` * :attr:`~django.db.models.Field.help_text`
* :attr:`~django.db.models.Field.db_column` * :attr:`~django.db.models.Field.db_column`
* :attr:`~django.db.models.Field.db_tablespace`: Currently only used with * :attr:`~django.db.models.Field.db_tablespace`: Only for index creation, if the
the Oracle backend and only for index creation. You can usually ignore backend supports :doc:`tablespaces </topics/db/tablespaces>`. You can usually
this option. ignore this option.
* :attr:`~django.db.models.Field.auto_created`: True if the field was * :attr:`~django.db.models.Field.auto_created`: True if the field was
automatically created, as for the `OneToOneField` used by model automatically created, as for the `OneToOneField` used by model
inheritance. For advanced use only. inheritance. For advanced use only.

View File

@ -646,49 +646,6 @@ The ``RETURNING INTO`` clause can be disabled by setting the
In this case, the Oracle backend will use a separate ``SELECT`` query to In this case, the Oracle backend will use a separate ``SELECT`` query to
retrieve AutoField values. retrieve AutoField values.
Tablespace options
------------------
A common paradigm for optimizing performance in Oracle-based systems is the
use of `tablespaces`_ to organize disk layout. The Oracle backend supports
this use case by adding ``db_tablespace`` options to the ``Meta`` and
``Field`` classes. (When you use a backend that lacks support for tablespaces,
Django ignores these options.)
.. _`tablespaces`: http://en.wikipedia.org/wiki/Tablespace
A tablespace can be specified for the table(s) generated by a model by
supplying the ``db_tablespace`` option inside the model's ``class Meta``.
Additionally, you can pass the ``db_tablespace`` option to a ``Field``
constructor to specify an alternate tablespace for the ``Field``'s column
index. If no index would be created for the column, the ``db_tablespace``
option is ignored::
class TablespaceExample(models.Model):
name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
data = models.CharField(max_length=255, db_index=True)
edges = models.ManyToManyField(to="self", db_tablespace="indexes")
class Meta:
db_tablespace = "tables"
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`` 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 also generate an index, but no tablespace for it is specified, so
it would be stored in the model tablespace ``tables`` by default.
Use the :setting:`DEFAULT_TABLESPACE` and :setting:`DEFAULT_INDEX_TABLESPACE`
settings to specify default values for the db_tablespace options.
These are useful for setting a tablespace for the built-in Django apps and
other applications whose code you cannot control.
Django does not create the tablespaces for you. Please refer to `Oracle's
documentation`_ for details on creating and managing tablespaces.
.. _`Oracle's documentation`: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_7003.htm#SQLRF01403
Naming issues Naming issues
------------- -------------

View File

@ -178,10 +178,11 @@ If ``True``, djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a
.. attribute:: Field.db_tablespace .. attribute:: Field.db_tablespace
The name of the database tablespace to use for this field's index, if this field The name of the :doc:`database tablespace </topics/db/tablespaces>` to use for
is indexed. The default is the project's :setting:`DEFAULT_INDEX_TABLESPACE` this field's index, if this field is indexed. The default is the project's
setting, if set, or the :attr:`~Field.db_tablespace` of the model, if any. If :setting:`DEFAULT_INDEX_TABLESPACE` setting, if set, or the
the backend doesn't support tablespaces, this option is ignored. :attr:`~Options.db_tablespace` of the model, if any. If the backend doesn't
support tablespaces for indexes, this option is ignored.
``default`` ``default``
----------- -----------

View File

@ -73,8 +73,10 @@ Django quotes column and table names behind the scenes.
.. attribute:: Options.db_tablespace .. attribute:: Options.db_tablespace
The name of the database tablespace to use for the model. If the backend The name of the :doc:`database tablespace </topics/db/tablespaces>` to use
doesn't support tablespaces, this option is ignored. for this model. The default is the project's :setting:`DEFAULT_TABLESPACE`
setting, if set. If the backend doesn't support tablespaces, this option is
ignored.
``get_latest_by`` ``get_latest_by``
----------------- -----------------

View File

@ -864,7 +864,7 @@ DEFAULT_INDEX_TABLESPACE
Default: ``''`` (Empty string) Default: ``''`` (Empty string)
Default tablespace to use for indexes on fields that don't specify Default tablespace to use for indexes on fields that don't specify
one, if the backend supports it. one, if the backend supports it (see :doc:`/topics/db/tablespaces`).
.. setting:: DEFAULT_TABLESPACE .. setting:: DEFAULT_TABLESPACE
@ -874,7 +874,7 @@ DEFAULT_TABLESPACE
Default: ``''`` (Empty string) Default: ``''`` (Empty string)
Default tablespace to use for models that don't specify one, if the Default tablespace to use for models that don't specify one, if the
backend supports it. backend supports it (see :doc:`/topics/db/tablespaces`).
.. setting:: DISALLOWED_USER_AGENTS .. setting:: DISALLOWED_USER_AGENTS

View File

@ -405,6 +405,8 @@ Django 1.4 also includes several smaller improvements worth noting:
code are slightly emphasized. This change makes it easier to scan a stacktrace code are slightly emphasized. This change makes it easier to scan a stacktrace
for issues in user code. for issues in user code.
* :doc:`Tablespace support </topics/db/tablespaces>` in PostgreSQL.
* Customizable names for :meth:`~django.template.Library.simple_tag`. * Customizable names for :meth:`~django.template.Library.simple_tag`.
* In the documentation, a helpful :doc:`security overview </topics/security>` * In the documentation, a helpful :doc:`security overview </topics/security>`

View File

@ -17,4 +17,5 @@ model maps to a single database table.
sql sql
transactions transactions
multi-db multi-db
tablespaces
optimization optimization

View File

@ -0,0 +1,73 @@
===========
Tablespaces
===========
A common paradigm for optimizing performance in database systems is the use of
`tablespaces`_ to organize disk layout.
.. _`tablespaces`: http://en.wikipedia.org/wiki/Tablespace
.. warning::
Django does not create the tablespaces for you. Please refer to your
database engine's documentation for details on creating and managing
tablespaces.
Declaring tablespaces for tables
--------------------------------
A tablespace can be specified for the table generated by a model by supplying
the :attr:`~django.db.models.Options.db_tablespace` option inside the model's
``class Meta``. This option also affects tables automatically created for
:class:`~django.db.models.ManyToManyField`\ s in the model.
You can use the :setting:`DEFAULT_TABLESPACE` setting to specify a default value
for :attr:`~django.db.models.Options.db_tablespace`. This is useful for setting
a tablespace for the built-in Django apps and other applications whose code you
cannot control.
Declaring tablespaces for indexes
---------------------------------
You can pass the :attr:`~django.db.models.Field.db_tablespace` option to a
``Field`` constructor to specify an alternate tablespace for the ``Field``'s
column index. If no index would be created for the column, the option is
ignored.
You can use the :setting:`DEFAULT_INDEX_TABLESPACE` setting to specify
a default value for :attr:`~django.db.models.Field.db_tablespace`.
If :attr:`~django.db.models.Field.db_tablespace` isn't specified and you didn't
set :setting:`DEFAULT_INDEX_TABLESPACE`, the index is created in the same
tablespace as the tables.
An example
----------
.. code-block:: python
class TablespaceExample(models.Model):
name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
data = models.CharField(max_length=255, db_index=True)
edges = models.ManyToManyField(to="self", db_tablespace="indexes")
class Meta:
db_tablespace = "tables"
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``
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
also generate an index, but no tablespace for it is specified, so it would be
stored in the model tablespace ``tables`` by default.
Database support
----------------
PostgreSQL and Oracle support tablespaces. SQLite and MySQL don't.
When you use a backend that lacks support for tablespaces, Django ignores all
tablespace-related options.
.. versionchanged:: 1.4
Since Django 1.4, the PostgreSQL backend supports tablespaces.

View File

View File

@ -0,0 +1,32 @@
from django.db import models
# Since the test database doesn't have tablespaces, it's impossible for Django
# to create the tables for models where db_tablespace is set. To avoid this
# problem, we mark the models as unmanaged, and temporarily revert them to
# managed during each tes. See setUp and tearDown -- it isn't possible to use
# setUpClass and tearDownClass because they're called before Django flushes the
# tables, so Django attempts to flush a non-existing table.
class ScientistRef(models.Model):
name = models.CharField(max_length=50)
class ArticleRef(models.Model):
title = models.CharField(max_length=50, unique=True)
code = models.CharField(max_length=50, unique=True)
authors = models.ManyToManyField(ScientistRef, related_name='articles_written_set')
reviewers = models.ManyToManyField(ScientistRef, related_name='articles_reviewed_set')
class Scientist(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_tablespace = 'tbl_tbsp'
managed = False
class Article(models.Model):
title = models.CharField(max_length=50, unique=True)
code = models.CharField(max_length=50, unique=True, db_tablespace='idx_tbsp')
authors = models.ManyToManyField(Scientist, related_name='articles_written_set')
reviewers = models.ManyToManyField(Scientist, related_name='articles_reviewed_set', db_tablespace='idx_tbsp')
class Meta:
db_tablespace = 'tbl_tbsp'
managed = False

View File

@ -0,0 +1,92 @@
import copy
from django.db import connection
from django.db import models
from django.db.models.loading import cache
from django.core.management.color import no_style
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from models import Article, ArticleRef, Scientist, ScientistRef
# Automatically created models
Authors = Article._meta.get_field('authors').rel.through
Reviewers = Article._meta.get_field('reviewers').rel.through
# We can't test the DEFAULT_TABLESPACE and DEFAULT_INDEX_TABLESPACE settings
# because they're evaluated when the model class is defined. As a consequence,
# @override_settings doesn't work.
def sql_for_table(model):
return '\n'.join(connection.creation.sql_create_model(model, no_style())[0])
def sql_for_index(model):
return '\n'.join(connection.creation.sql_indexes_for_model(model, no_style()))
class TablespacesTests(TestCase):
def setUp(self):
# The unmanaged models need to be removed after the test in order to
# prevent bad interactions with other tests (proxy_models_inheritance).
self.old_app_models = copy.deepcopy(cache.app_models)
self.old_app_store = copy.deepcopy(cache.app_store)
for model in Article, Authors, Reviewers, Scientist:
model._meta.managed = True
def tearDown(self):
for model in Article, Authors, Reviewers, Scientist:
model._meta.managed = False
cache.app_models = self.old_app_models
cache.app_store = self.old_app_store
cache._get_models_cache = {}
def assertNumContains(self, haystack, needle, count):
real_count = haystack.count(needle)
self.assertEqual(real_count, count, "Found %d instances of '%s', "
"expected %d" % (real_count, needle, count))
@skipUnlessDBFeature('supports_tablespaces')
def test_tablespace_for_model(self):
# 1 for the table + 1 for the index on the primary key
self.assertNumContains(sql_for_table(Scientist).lower(), 'tbl_tbsp', 2)
@skipIfDBFeature('supports_tablespaces')
def test_tablespace_ignored_for_model(self):
# No tablespace-related SQL
self.assertEqual(sql_for_table(Scientist),
sql_for_table(ScientistRef).replace('ref', ''))
@skipUnlessDBFeature('supports_tablespaces')
def test_tablespace_for_indexed_field(self):
# 1 for the table + 1 for the primary key + 1 for the index on name
self.assertNumContains(sql_for_table(Article).lower(), 'tbl_tbsp', 3)
# 1 for the index on reference
self.assertNumContains(sql_for_table(Article).lower(), 'idx_tbsp', 1)
@skipIfDBFeature('supports_tablespaces')
def test_tablespace_ignored_for_indexed_field(self):
# No tablespace-related SQL
self.assertEqual(sql_for_table(Article),
sql_for_table(ArticleRef).replace('ref', ''))
@skipUnlessDBFeature('supports_tablespaces')
def test_tablespace_for_many_to_many_field(self):
# The join table of the ManyToManyField always goes to the tablespace
# of the model.
self.assertNumContains(sql_for_table(Authors).lower(), 'tbl_tbsp', 2)
self.assertNumContains(sql_for_table(Authors).lower(), 'idx_tbsp', 0)
# The ManyToManyField declares no db_tablespace, indexes for the two
# foreign keys in the join table go to the tablespace of the model.
self.assertNumContains(sql_for_index(Authors).lower(), 'tbl_tbsp', 2)
self.assertNumContains(sql_for_index(Authors).lower(), 'idx_tbsp', 0)
# The join table of the ManyToManyField always goes to the tablespace
# of the model.
self.assertNumContains(sql_for_table(Reviewers).lower(), 'tbl_tbsp', 2)
self.assertNumContains(sql_for_table(Reviewers).lower(), 'idx_tbsp', 0)
# The ManyToManyField declares db_tablespace, indexes for the two
# foreign keys in the join table go to this tablespace.
self.assertNumContains(sql_for_index(Reviewers).lower(), 'tbl_tbsp', 0)
self.assertNumContains(sql_for_index(Reviewers).lower(), 'idx_tbsp', 2)