Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 246580573d7f1226aca39615dbf39956c520e6d7 1 parent 69e1e61
Aymeric Augustin authored October 14, 2011
12  django/db/backends/__init__.py
@@ -365,6 +365,10 @@ class BaseDatabaseFeatures(object):
365 365
     # date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas
366 366
     supports_mixed_date_datetime_comparisons = True
367 367
 
  368
+    # Does the backend support tablespaces? Default to False because it isn't
  369
+    # in the SQL standard.
  370
+    supports_tablespaces = False
  371
+
368 372
     # Features that need to be confirmed at runtime
369 373
     # Cache whether the confirmation has been performed.
370 374
     _confirmed = False
@@ -696,8 +700,12 @@ def end_transaction_sql(self, success=True):
696 700
 
697 701
     def tablespace_sql(self, tablespace, inline=False):
698 702
         """
699  
-        Returns the SQL that will be appended to tables or rows to define
700  
-        a tablespace. Returns '' if the backend doesn't use tablespaces.
  703
+        Returns the SQL that will be used in a query to define the tablespace.
  704
+
  705
+        Returns '' if the backend doesn't support tablespaces.
  706
+
  707
+        If inline is True, the SQL is appended to a row; otherwise it's appended
  708
+        to the entire CREATE TABLE or CREATE INDEX statement.
701 709
         """
702 710
         return ''
703 711
 
16  django/db/backends/creation.py
@@ -57,7 +57,9 @@ def sql_create_model(self, model, style, known_models=set()):
57 57
             if tablespace and f.unique:
58 58
                 # We must specify the index tablespace inline, because we
59 59
                 # won't be generating a CREATE INDEX statement for this field.
60  
-                field_output.append(self.connection.ops.tablespace_sql(tablespace, inline=True))
  60
+                tablespace_sql = self.connection.ops.tablespace_sql(tablespace, inline=True)
  61
+                if tablespace_sql:
  62
+                    field_output.append(tablespace_sql)
61 63
             if f.rel:
62 64
                 ref_output, pending = self.sql_for_inline_foreign_key_references(f, known_models, style)
63 65
                 if pending:
@@ -74,7 +76,9 @@ def sql_create_model(self, model, style, known_models=set()):
74 76
             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
75 77
         full_statement.append(')')
76 78
         if opts.db_tablespace:
77  
-            full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
  79
+            tablespace_sql = self.connection.ops.tablespace_sql(opts.db_tablespace)
  80
+            if tablespace_sql:
  81
+                full_statement.append(tablespace_sql)
78 82
         full_statement.append(';')
79 83
         final_output.append('\n'.join(full_statement))
80 84
 
@@ -149,11 +153,9 @@ def sql_indexes_for_field(self, model, f, style):
149 153
             qn = self.connection.ops.quote_name
150 154
             tablespace = f.db_tablespace or model._meta.db_tablespace
151 155
             if tablespace:
152  
-                sql = self.connection.ops.tablespace_sql(tablespace)
153  
-                if sql:
154  
-                    tablespace_sql = ' ' + sql
155  
-                else:
156  
-                    tablespace_sql = ''
  156
+                tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
  157
+                if tablespace_sql:
  158
+                    tablespace_sql = ' ' + tablespace_sql
157 159
             else:
158 160
                 tablespace_sql = ''
159 161
             i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))
7  django/db/backends/oracle/base.py
@@ -79,6 +79,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
79 79
     can_defer_constraint_checks = True
80 80
     ignores_nulls_in_unique_constraints = False
81 81
     has_bulk_insert = True
  82
+    supports_tablespaces = True
82 83
 
83 84
 class DatabaseOperations(BaseDatabaseOperations):
84 85
     compiler_module = "django.db.backends.oracle.compiler"
@@ -326,8 +327,10 @@ def start_transaction_sql(self):
326 327
         return ''
327 328
 
328 329
     def tablespace_sql(self, tablespace, inline=False):
329  
-        return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""),
330  
-            self.quote_name(tablespace))
  330
+        if inline:
  331
+            return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace)
  332
+        else:
  333
+            return "TABLESPACE %s" % self.quote_name(tablespace)
331 334
 
332 335
     def value_to_db_datetime(self, value):
333 336
         # Oracle doesn't support tz-aware datetimes
2  django/db/backends/postgresql_psycopg2/base.py
@@ -75,7 +75,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
75 75
     has_select_for_update = True
76 76
     has_select_for_update_nowait = True
77 77
     has_bulk_insert = True
78  
-
  78
+    supports_tablespaces = True
79 79
 
80 80
 class DatabaseWrapper(BaseDatabaseWrapper):
81 81
     vendor = 'postgresql'
8  django/db/backends/postgresql_psycopg2/creation.py
@@ -44,11 +44,9 @@ def sql_indexes_for_field(self, model, f, style):
44 44
             db_table = model._meta.db_table
45 45
             tablespace = f.db_tablespace or model._meta.db_tablespace
46 46
             if tablespace:
47  
-                sql = self.connection.ops.tablespace_sql(tablespace)
48  
-                if sql:
49  
-                    tablespace_sql = ' ' + sql
50  
-                else:
51  
-                    tablespace_sql = ''
  47
+                tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
  48
+                if tablespace_sql:
  49
+                    tablespace_sql = ' ' + tablespace_sql
52 50
             else:
53 51
                 tablespace_sql = ''
54 52
 
6  django/db/backends/postgresql_psycopg2/operations.py
@@ -99,6 +99,12 @@ def sql_flush(self, style, tables, sequences):
99 99
         else:
100 100
             return []
101 101
 
  102
+    def tablespace_sql(self, tablespace, inline=False):
  103
+        if inline:
  104
+            return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace)
  105
+        else:
  106
+            return "TABLESPACE %s" % self.quote_name(tablespace)
  107
+
102 108
     def sequence_reset_sql(self, style, model_list):
103 109
         from django.db import models
104 110
         output = []
5  django/db/models/fields/related.py
@@ -1078,6 +1078,7 @@ def set_managed(field, model, cls):
@@ -1086,8 +1087,8 @@ def set_managed(field, model, cls):
6  docs/howto/custom-model-fields.txt
@@ -219,9 +219,9 @@ parameters:
219 219
 * :attr:`~django.db.models.Field.choices`
220 220
 * :attr:`~django.db.models.Field.help_text`
221 221
 * :attr:`~django.db.models.Field.db_column`
222  
-* :attr:`~django.db.models.Field.db_tablespace`: Currently only used with
223  
-  the Oracle backend and only for index creation. You can usually ignore
224  
-  this option.
  222
+* :attr:`~django.db.models.Field.db_tablespace`: Only for index creation, if the
  223
+  backend supports :doc:`tablespaces </topics/db/tablespaces>`. You can usually
  224
+  ignore this option.
225 225
 * :attr:`~django.db.models.Field.auto_created`: True if the field was
226 226
   automatically created, as for the `OneToOneField` used by model
227 227
   inheritance. For advanced use only.
43  docs/ref/databases.txt
@@ -646,49 +646,6 @@ The ``RETURNING INTO`` clause can be disabled by setting the
646 646
 In this case, the Oracle backend will use a separate ``SELECT`` query to
647 647
 retrieve AutoField values.
648 648
 
649  
-Tablespace options
650  
-------------------
651  
-
652  
-A common paradigm for optimizing performance in Oracle-based systems is the
653  
-use of `tablespaces`_ to organize disk layout. The Oracle backend supports
654  
-this use case by adding ``db_tablespace`` options to the ``Meta`` and
655  
-``Field`` classes.  (When you use a backend that lacks support for tablespaces,
656  
-Django ignores these options.)
657  
-
658  
-.. _`tablespaces`: http://en.wikipedia.org/wiki/Tablespace
659  
-
660  
-A tablespace can be specified for the table(s) generated by a model by
661  
-supplying the ``db_tablespace`` option inside the model's ``class Meta``.
662  
-Additionally, you can pass the ``db_tablespace`` option to a ``Field``
663  
-constructor to specify an alternate tablespace for the ``Field``'s column
664  
-index. If no index would be created for the column, the ``db_tablespace``
665  
-option is ignored::
666  
-
667  
-    class TablespaceExample(models.Model):
668  
-        name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
669  
-        data = models.CharField(max_length=255, db_index=True)
670  
-        edges = models.ManyToManyField(to="self", db_tablespace="indexes")
671  
-
672  
-        class Meta:
673  
-            db_tablespace = "tables"
674  
-
675  
-In this example, the tables generated by the ``TablespaceExample`` model
676  
-(i.e., the model table and the many-to-many table) would be stored in the
677  
-``tables`` tablespace. The index for the name field and the indexes on the
678  
-many-to-many table would be stored in the ``indexes`` tablespace. The ``data``
679  
-field would also generate an index, but no tablespace for it is specified, so
680  
-it would be stored in the model tablespace ``tables`` by default.
681  
-
682  
-Use the :setting:`DEFAULT_TABLESPACE` and :setting:`DEFAULT_INDEX_TABLESPACE`
683  
-settings to specify default values for the db_tablespace options.
684  
-These are useful for setting a tablespace for the built-in Django apps and
685  
-other applications whose code you cannot control.
686  
-
687  
-Django does not create the tablespaces for you. Please refer to `Oracle's
688  
-documentation`_ for details on creating and managing tablespaces.
689  
-
690  
-.. _`Oracle's documentation`: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_7003.htm#SQLRF01403
691  
-
692 649
 Naming issues
693 650
 -------------
694 651
 
9  docs/ref/models/fields.txt
@@ -178,10 +178,11 @@ If ``True``, djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a
178 178
 
179 179
 .. attribute:: Field.db_tablespace
180 180
 
181  
-The name of the database tablespace to use for this field's index, if this field
182  
-is indexed. The default is the project's :setting:`DEFAULT_INDEX_TABLESPACE`
183  
-setting, if set, or the :attr:`~Field.db_tablespace` of the model, if any. If
184  
-the backend doesn't support tablespaces, this option is ignored.
  181
+The name of the :doc:`database tablespace </topics/db/tablespaces>` to use for
  182
+this field's index, if this field is indexed. The default is the project's
  183
+:setting:`DEFAULT_INDEX_TABLESPACE` setting, if set, or the
  184
+:attr:`~Options.db_tablespace` of the model, if any. If the backend doesn't
  185
+support tablespaces for indexes, this option is ignored.
185 186
 
186 187
 ``default``
187 188
 -----------
6  docs/ref/models/options.txt
@@ -73,8 +73,10 @@ Django quotes column and table names behind the scenes.
73 73
 
74 74
 .. attribute:: Options.db_tablespace
75 75
 
76  
-    The name of the database tablespace to use for the model. If the backend
77  
-    doesn't support tablespaces, this option is ignored.
  76
+    The name of the :doc:`database tablespace </topics/db/tablespaces>` to use
  77
+    for this model. The default is the project's :setting:`DEFAULT_TABLESPACE`
  78
+    setting, if set. If the backend doesn't support tablespaces, this option is
  79
+    ignored.
78 80
 
79 81
 ``get_latest_by``
80 82
 -----------------
4  docs/ref/settings.txt
@@ -864,7 +864,7 @@ DEFAULT_INDEX_TABLESPACE
864 864
 Default: ``''`` (Empty string)
865 865
 
866 866
 Default tablespace to use for indexes on fields that don't specify
867  
-one, if the backend supports it.
  867
+one, if the backend supports it (see :doc:`/topics/db/tablespaces`).
868 868
 
869 869
 .. setting:: DEFAULT_TABLESPACE
870 870
 
@@ -874,7 +874,7 @@ DEFAULT_TABLESPACE
874 874
 Default: ``''`` (Empty string)
875 875
 
876 876
 Default tablespace to use for models that don't specify one, if the
877  
-backend supports it.
  877
+backend supports it (see :doc:`/topics/db/tablespaces`).
878 878
 
879 879
 .. setting:: DISALLOWED_USER_AGENTS
880 880
 
2  docs/releases/1.4.txt
@@ -405,6 +405,8 @@ Django 1.4 also includes several smaller improvements worth noting:
405 405
   code are slightly emphasized. This change makes it easier to scan a stacktrace
406 406
   for issues in user code.
407 407
 
  408
+* :doc:`Tablespace support </topics/db/tablespaces>` in PostgreSQL.
  409
+
408 410
 * Customizable names for :meth:`~django.template.Library.simple_tag`.
409 411
 
410 412
 * In the documentation, a helpful :doc:`security overview </topics/security>`
1  docs/topics/db/index.txt
@@ -17,4 +17,5 @@ model maps to a single database table.
17 17
    sql
18 18
    transactions
19 19
    multi-db
  20
+   tablespaces
20 21
    optimization
73  docs/topics/db/tablespaces.txt
... ...
@@ -0,0 +1,73 @@
  1
+===========
  2
+Tablespaces
  3
+===========
  4
+
  5
+A common paradigm for optimizing performance in database systems is the use of
  6
+`tablespaces`_ to organize disk layout.
  7
+
  8
+.. _`tablespaces`: http://en.wikipedia.org/wiki/Tablespace
  9
+
  10
+.. warning::
  11
+    Django does not create the tablespaces for you. Please refer to your
  12
+    database engine's documentation for details on creating and managing
  13
+    tablespaces.
  14
+
  15
+
  16
+Declaring tablespaces for tables
  17
+--------------------------------
  18
+
  19
+A tablespace can be specified for the table generated by a model by supplying
  20
+the :attr:`~django.db.models.Options.db_tablespace` option inside the model's
  21
+``class Meta``. This option also affects tables automatically created for
  22
+:class:`~django.db.models.ManyToManyField`\ s in the model.
  23
+
  24
+You can use the :setting:`DEFAULT_TABLESPACE` setting to specify a default value
  25
+for :attr:`~django.db.models.Options.db_tablespace`. This is useful for setting
  26
+a tablespace for the built-in Django apps and other applications whose code you
  27
+cannot control.
  28
+
  29
+Declaring tablespaces for indexes
  30
+---------------------------------
  31
+
  32
+You can pass the :attr:`~django.db.models.Field.db_tablespace` option to a
  33
+``Field`` constructor to specify an alternate tablespace for the ``Field``'s
  34
+column index. If no index would be created for the column, the option is
  35
+ignored.
  36
+
  37
+You can use the :setting:`DEFAULT_INDEX_TABLESPACE` setting to specify
  38
+a default value for :attr:`~django.db.models.Field.db_tablespace`.
  39
+
  40
+If :attr:`~django.db.models.Field.db_tablespace` isn't specified and you didn't
  41
+set :setting:`DEFAULT_INDEX_TABLESPACE`, the index is created in the same
  42
+tablespace as the tables.
  43
+
  44
+An example
  45
+----------
  46
+
  47
+.. code-block:: python
  48
+
  49
+    class TablespaceExample(models.Model):
  50
+        name = models.CharField(max_length=30, db_index=True, db_tablespace="indexes")
  51
+        data = models.CharField(max_length=255, db_index=True)
  52
+        edges = models.ManyToManyField(to="self", db_tablespace="indexes")
  53
+
  54
+        class Meta:
  55
+            db_tablespace = "tables"
  56
+
  57
+In this example, the tables generated by the ``TablespaceExample`` model (i.e.
  58
+the model table and the many-to-many table) would be stored in the ``tables``
  59
+tablespace. The index for the name field and the indexes on the many-to-many
  60
+table would be stored in the ``indexes`` tablespace. The ``data`` field would
  61
+also generate an index, but no tablespace for it is specified, so it would be
  62
+stored in the model tablespace ``tables`` by default.
  63
+
  64
+Database support
  65
+----------------
  66
+
  67
+PostgreSQL and Oracle support tablespaces. SQLite and MySQL don't.
  68
+
  69
+When you use a backend that lacks support for tablespaces, Django ignores all
  70
+tablespace-related options.
  71
+
  72
+.. versionchanged:: 1.4
  73
+    Since Django 1.4, the PostgreSQL backend supports tablespaces.
0  tests/modeltests/tablespaces/__init__.py
No changes.
32  tests/modeltests/tablespaces/models.py
... ...
@@ -0,0 +1,32 @@
  1
+from django.db import models
  2
+
  3
+# Since the test database doesn't have tablespaces, it's impossible for Django
  4
+# to create the tables for models where db_tablespace is set. To avoid this
  5
+# problem, we mark the models as unmanaged, and temporarily revert them to
  6
+# managed during each tes. See setUp and tearDown -- it isn't possible to use
  7
+# setUpClass and tearDownClass because they're called before Django flushes the
  8
+# tables, so Django attempts to flush a non-existing table.
  9
+
  10
+class ScientistRef(models.Model):
  11
+    name = models.CharField(max_length=50)
  12
+
  13
+class ArticleRef(models.Model):
  14
+    title = models.CharField(max_length=50, unique=True)
  15
+    code = models.CharField(max_length=50, unique=True)
  16
+    authors = models.ManyToManyField(ScientistRef, related_name='articles_written_set')
  17
+    reviewers = models.ManyToManyField(ScientistRef, related_name='articles_reviewed_set')
  18
+
  19
+class Scientist(models.Model):
  20
+    name = models.CharField(max_length=50)
  21
+    class Meta:
  22
+        db_tablespace = 'tbl_tbsp'
  23
+        managed = False
  24
+
  25
+class Article(models.Model):
  26
+    title = models.CharField(max_length=50, unique=True)
  27
+    code = models.CharField(max_length=50, unique=True, db_tablespace='idx_tbsp')
  28
+    authors = models.ManyToManyField(Scientist, related_name='articles_written_set')
  29
+    reviewers = models.ManyToManyField(Scientist, related_name='articles_reviewed_set', db_tablespace='idx_tbsp')
  30
+    class Meta:
  31
+        db_tablespace = 'tbl_tbsp'
  32
+        managed = False
92  tests/modeltests/tablespaces/tests.py
... ...
@@ -0,0 +1,92 @@
  1
+import copy
  2
+
  3
+from django.db import connection
  4
+from django.db import models
  5
+from django.db.models.loading import cache
  6
+from django.core.management.color import no_style 
  7
+from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
  8
+
  9
+from models import Article, ArticleRef, Scientist, ScientistRef
  10
+
  11
+# Automatically created models
  12
+Authors = Article._meta.get_field('authors').rel.through
  13
+Reviewers = Article._meta.get_field('reviewers').rel.through
  14
+
  15
+# We can't test the DEFAULT_TABLESPACE and DEFAULT_INDEX_TABLESPACE settings
  16
+# because they're evaluated when the model class is defined. As a consequence,
  17
+# @override_settings doesn't work.
  18
+
  19
+def sql_for_table(model):
  20
+    return '\n'.join(connection.creation.sql_create_model(model, no_style())[0])
  21
+
  22
+def sql_for_index(model):
  23
+    return '\n'.join(connection.creation.sql_indexes_for_model(model, no_style()))
  24
+
  25
+
  26
+class TablespacesTests(TestCase):
  27
+
  28
+    def setUp(self):
  29
+        # The unmanaged models need to be removed after the test in order to
  30
+        # prevent bad interactions with other tests (proxy_models_inheritance).
  31
+        self.old_app_models = copy.deepcopy(cache.app_models)
  32
+        self.old_app_store = copy.deepcopy(cache.app_store)
  33
+
  34
+        for model in Article, Authors, Reviewers, Scientist:
  35
+            model._meta.managed = True
  36
+
  37
+    def tearDown(self):
  38
+        for model in Article, Authors, Reviewers, Scientist:
  39
+            model._meta.managed = False
  40
+
  41
+        cache.app_models = self.old_app_models
  42
+        cache.app_store = self.old_app_store
  43
+        cache._get_models_cache = {}
  44
+
  45
+    def assertNumContains(self, haystack, needle, count):
  46
+        real_count = haystack.count(needle)
  47
+        self.assertEqual(real_count, count, "Found %d instances of '%s', "
  48
+                "expected %d" % (real_count, needle, count))
  49
+
  50
+    @skipUnlessDBFeature('supports_tablespaces')
  51
+    def test_tablespace_for_model(self):
  52
+        # 1 for the table + 1 for the index on the primary key
  53
+        self.assertNumContains(sql_for_table(Scientist).lower(), 'tbl_tbsp', 2)
  54
+
  55
+    @skipIfDBFeature('supports_tablespaces')
  56
+    def test_tablespace_ignored_for_model(self):
  57
+        # No tablespace-related SQL
  58
+        self.assertEqual(sql_for_table(Scientist),
  59
+                         sql_for_table(ScientistRef).replace('ref', ''))
  60
+
  61
+    @skipUnlessDBFeature('supports_tablespaces')
  62
+    def test_tablespace_for_indexed_field(self):
  63
+        # 1 for the table + 1 for the primary key + 1 for the index on name
  64
+        self.assertNumContains(sql_for_table(Article).lower(), 'tbl_tbsp', 3)
  65
+        # 1 for the index on reference
  66
+        self.assertNumContains(sql_for_table(Article).lower(), 'idx_tbsp', 1)
  67
+
  68
+    @skipIfDBFeature('supports_tablespaces')
  69
+    def test_tablespace_ignored_for_indexed_field(self):
  70
+        # No tablespace-related SQL
  71
+        self.assertEqual(sql_for_table(Article),
  72
+                         sql_for_table(ArticleRef).replace('ref', ''))
  73
+
  74
+    @skipUnlessDBFeature('supports_tablespaces')
  75
+    def test_tablespace_for_many_to_many_field(self):
  76
+        # The join table of the ManyToManyField always goes to the tablespace
  77
+        # of the model.
  78
+        self.assertNumContains(sql_for_table(Authors).lower(), 'tbl_tbsp', 2)
  79
+        self.assertNumContains(sql_for_table(Authors).lower(), 'idx_tbsp', 0)
  80
+        # The ManyToManyField declares no db_tablespace, indexes for the two
  81
+        # foreign keys in the join table go to the tablespace of the model.
  82
+        self.assertNumContains(sql_for_index(Authors).lower(), 'tbl_tbsp', 2)
  83
+        self.assertNumContains(sql_for_index(Authors).lower(), 'idx_tbsp', 0)
  84
+
  85
+        # The join table of the ManyToManyField always goes to the tablespace
  86
+        # of the model.
  87
+        self.assertNumContains(sql_for_table(Reviewers).lower(), 'tbl_tbsp', 2)
  88
+        self.assertNumContains(sql_for_table(Reviewers).lower(), 'idx_tbsp', 0)
  89
+        # The ManyToManyField declares db_tablespace, indexes for the two
  90
+        # foreign keys in the join table go to this tablespace.
  91
+        self.assertNumContains(sql_for_index(Reviewers).lower(), 'tbl_tbsp', 0)
  92
+        self.assertNumContains(sql_for_index(Reviewers).lower(), 'idx_tbsp', 2)

0 notes on commit 2465805

Please sign in to comment.
Something went wrong with that request. Please try again.