Fixed #27061 -- Added a TEST['TEMPLATE'] setting for PostgreSQL.

This commit is contained in:
Chris Jerdonek 2016-08-15 07:10:40 -07:00 committed by Tim Graham
parent bc1e2d8e8e
commit a3db480393
5 changed files with 98 additions and 10 deletions

View File

@ -5,28 +5,42 @@ from django.db.backends.base.creation import BaseDatabaseCreation
class DatabaseCreation(BaseDatabaseCreation): class DatabaseCreation(BaseDatabaseCreation):
def _quote_name(self, name):
return self.connection.ops.quote_name(name)
def _get_database_create_suffix(self, encoding=None, template=None):
suffix = ""
if encoding:
suffix += " ENCODING '{}'".format(encoding)
if template:
suffix += " TEMPLATE {}".format(self._quote_name(template))
if suffix:
suffix = "WITH" + suffix
return suffix
def sql_table_creation_suffix(self): def sql_table_creation_suffix(self):
test_settings = self.connection.settings_dict['TEST'] test_settings = self.connection.settings_dict['TEST']
assert test_settings['COLLATION'] is None, ( assert test_settings['COLLATION'] is None, (
"PostgreSQL does not support collation setting at database creation time." "PostgreSQL does not support collation setting at database creation time."
) )
if test_settings['CHARSET']: return self._get_database_create_suffix(
return "WITH ENCODING '%s'" % test_settings['CHARSET'] encoding=test_settings['CHARSET'],
return '' template=test_settings.get('TEMPLATE'),
)
def _clone_test_db(self, number, verbosity, keepdb=False): def _clone_test_db(self, number, verbosity, keepdb=False):
# CREATE DATABASE ... WITH TEMPLATE ... requires closing connections # CREATE DATABASE ... WITH TEMPLATE ... requires closing connections
# to the template database. # to the template database.
self.connection.close() self.connection.close()
qn = self.connection.ops.quote_name
source_database_name = self.connection.settings_dict['NAME'] source_database_name = self.connection.settings_dict['NAME']
target_database_name = self.get_test_db_clone_settings(number)['NAME'] target_database_name = self.get_test_db_clone_settings(number)['NAME']
suffix = self._get_database_create_suffix(template=source_database_name)
creation_sql = "CREATE DATABASE {} {}".format(self._quote_name(target_database_name), suffix)
with self._nodb_connection.cursor() as cursor: with self._nodb_connection.cursor() as cursor:
try: try:
cursor.execute("CREATE DATABASE %s WITH TEMPLATE %s" % ( cursor.execute(creation_sql)
qn(target_database_name), qn(source_database_name)))
except Exception as e: except Exception as e:
if keepdb: if keepdb:
return return
@ -35,9 +49,8 @@ class DatabaseCreation(BaseDatabaseCreation):
print("Destroying old test database for alias %s..." % ( print("Destroying old test database for alias %s..." % (
self._get_database_display_str(verbosity, target_database_name), self._get_database_display_str(verbosity, target_database_name),
)) ))
cursor.execute("DROP DATABASE %s" % qn(target_database_name)) cursor.execute("DROP DATABASE %s" % self._quote_name(target_database_name))
cursor.execute("CREATE DATABASE %s WITH TEMPLATE %s" % ( cursor.execute(creation_sql)
qn(target_database_name), qn(source_database_name)))
except Exception as e: except Exception as e:
sys.stderr.write("Got an error cloning the test database: %s\n" % e) sys.stderr.write("Got an error cloning the test database: %s\n" % e)
sys.exit(2) sys.exit(2)

View File

@ -164,6 +164,16 @@ lookups that use the ``LIKE`` operator in their SQL, as is done with the
.. _PostgreSQL operator class: http://www.postgresql.org/docs/current/static/indexes-opclass.html .. _PostgreSQL operator class: http://www.postgresql.org/docs/current/static/indexes-opclass.html
Test database templates
-----------------------
.. versionadded:: 1.11
You can use the :setting:`TEST['TEMPLATE'] <TEST_TEMPLATE>` setting to specify
a `template`_ (e.g. ``'template0'``) from which to create a test database.
.. _template: https://www.postgresql.org/docs/current/static/sql-createdatabase.html
Speeding up test execution with non-durable settings Speeding up test execution with non-durable settings
---------------------------------------------------- ----------------------------------------------------

View File

@ -738,6 +738,20 @@ the database state between tests if you don't have transactions). You can set
this to ``False`` to speed up creation time if you don't have any test classes this to ``False`` to speed up creation time if you don't have any test classes
with :ref:`serialized_rollback=True <test-case-serialized-rollback>`. with :ref:`serialized_rollback=True <test-case-serialized-rollback>`.
.. setting:: TEST_TEMPLATE
``TEMPLATE``
^^^^^^^^^^^^
.. versionadded:: 1.11
This is a PostgreSQL-specific setting.
The name of a `template`_ (e.g. ``'template0'``) from which to create the test
database.
.. _template: https://www.postgresql.org/docs/current/static/sql-createdatabase.html
.. setting:: TEST_CREATE .. setting:: TEST_CREATE
``CREATE_DB`` ``CREATE_DB``

View File

@ -159,6 +159,9 @@ Database backends
on PostgreSQL 9.5+ and Oracle to execute queries with on PostgreSQL 9.5+ and Oracle to execute queries with
``FOR UPDATE SKIP LOCKED``. ``FOR UPDATE SKIP LOCKED``.
* Added the :setting:`TEST['TEMPLATE'] <TEST_TEMPLATE>` setting to let
PostgreSQL users specify a template for creating the test database.
Email Email
~~~~~ ~~~~~

View File

@ -1,9 +1,12 @@
import copy import copy
import unittest
from contextlib import contextmanager
from django.db import DEFAULT_DB_ALIAS, connections from django.db import DEFAULT_DB_ALIAS, connection, connections
from django.db.backends.base.creation import ( from django.db.backends.base.creation import (
TEST_DATABASE_PREFIX, BaseDatabaseCreation, TEST_DATABASE_PREFIX, BaseDatabaseCreation,
) )
from django.db.backends.postgresql.creation import DatabaseCreation
from django.test import SimpleTestCase from django.test import SimpleTestCase
@ -40,3 +43,48 @@ class TestDbSignatureTests(SimpleTestCase):
test_connection.settings_dict['TEST'] = {'NAME': test_name} test_connection.settings_dict['TEST'] = {'NAME': test_name}
signature = BaseDatabaseCreation(test_connection).test_db_signature() signature = BaseDatabaseCreation(test_connection).test_db_signature()
self.assertEqual(signature[3], test_name) self.assertEqual(signature[3], test_name)
@unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL-specific tests")
class PostgreSQLDatabaseCreationTests(SimpleTestCase):
@contextmanager
def changed_test_settings(self, **kwargs):
settings = connection.settings_dict['TEST']
saved_values = {}
for name in kwargs:
if name in settings:
saved_values[name] = settings[name]
for name, value in kwargs.items():
settings[name] = value
try:
yield
finally:
for name, value in kwargs.items():
if name in saved_values:
settings[name] = saved_values[name]
else:
del settings[name]
def check_sql_table_creation_suffix(self, settings, expected):
with self.changed_test_settings(**settings):
creation = DatabaseCreation(connection)
suffix = creation.sql_table_creation_suffix()
self.assertEqual(suffix, expected)
def test_sql_table_creation_suffix_with_none_settings(self):
settings = dict(CHARSET=None, TEMPLATE=None)
self.check_sql_table_creation_suffix(settings, "")
def test_sql_table_creation_suffix_with_encoding(self):
settings = dict(CHARSET='UTF8')
self.check_sql_table_creation_suffix(settings, "WITH ENCODING 'UTF8'")
def test_sql_table_creation_suffix_with_template(self):
settings = dict(TEMPLATE='template0')
self.check_sql_table_creation_suffix(settings, 'WITH TEMPLATE "template0"')
def test_sql_table_creation_suffix_with_encoding_and_template(self):
settings = dict(CHARSET='UTF8', TEMPLATE='template0')
self.check_sql_table_creation_suffix(settings, '''WITH ENCODING 'UTF8' TEMPLATE "template0"''')