测试gitnore
This commit is contained in:
@@ -6,10 +6,7 @@ import warnings
|
||||
from collections import deque
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
@@ -23,21 +20,11 @@ from django.utils import timezone
|
||||
from django.utils.asyncio import async_unsafe
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
NO_DB_ALIAS = "__no_db__"
|
||||
|
||||
|
||||
# RemovedInDjango50Warning
|
||||
def timezone_constructor(tzname):
|
||||
if settings.USE_DEPRECATED_PYTZ:
|
||||
import pytz
|
||||
|
||||
return pytz.timezone(tzname)
|
||||
return zoneinfo.ZoneInfo(tzname)
|
||||
NO_DB_ALIAS = '__no_db__'
|
||||
|
||||
|
||||
class BaseDatabaseWrapper:
|
||||
"""Represent a database connection."""
|
||||
|
||||
# Mapping of Field objects to their column types.
|
||||
data_types = {}
|
||||
# Mapping of Field objects to their SQL suffix such as AUTOINCREMENT.
|
||||
@@ -45,8 +32,8 @@ class BaseDatabaseWrapper:
|
||||
# Mapping of Field objects to their SQL for CHECK constraints.
|
||||
data_type_check_constraints = {}
|
||||
ops = None
|
||||
vendor = "unknown"
|
||||
display_name = "unknown"
|
||||
vendor = 'unknown'
|
||||
display_name = 'unknown'
|
||||
SchemaEditorClass = None
|
||||
# Classes instantiated in __init__().
|
||||
client_class = None
|
||||
@@ -145,10 +132,10 @@ class BaseDatabaseWrapper:
|
||||
"""
|
||||
if not settings.USE_TZ:
|
||||
return None
|
||||
elif self.settings_dict["TIME_ZONE"] is None:
|
||||
elif self.settings_dict['TIME_ZONE'] is None:
|
||||
return timezone.utc
|
||||
else:
|
||||
return timezone_constructor(self.settings_dict["TIME_ZONE"])
|
||||
return pytz.timezone(self.settings_dict['TIME_ZONE'])
|
||||
|
||||
@cached_property
|
||||
def timezone_name(self):
|
||||
@@ -157,10 +144,10 @@ class BaseDatabaseWrapper:
|
||||
"""
|
||||
if not settings.USE_TZ:
|
||||
return settings.TIME_ZONE
|
||||
elif self.settings_dict["TIME_ZONE"] is None:
|
||||
return "UTC"
|
||||
elif self.settings_dict['TIME_ZONE'] is None:
|
||||
return 'UTC'
|
||||
else:
|
||||
return self.settings_dict["TIME_ZONE"]
|
||||
return self.settings_dict['TIME_ZONE']
|
||||
|
||||
@property
|
||||
def queries_logged(self):
|
||||
@@ -171,38 +158,26 @@ class BaseDatabaseWrapper:
|
||||
if len(self.queries_log) == self.queries_log.maxlen:
|
||||
warnings.warn(
|
||||
"Limit for query logging exceeded, only the last {} queries "
|
||||
"will be returned.".format(self.queries_log.maxlen)
|
||||
)
|
||||
"will be returned.".format(self.queries_log.maxlen))
|
||||
return list(self.queries_log)
|
||||
|
||||
# ##### Backend-specific methods for creating connections and cursors #####
|
||||
|
||||
def get_connection_params(self):
|
||||
"""Return a dict of parameters suitable for get_new_connection."""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require a get_connection_params() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_connection_params() method')
|
||||
|
||||
def get_new_connection(self, conn_params):
|
||||
"""Open a connection to the database."""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require a get_new_connection() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_new_connection() method')
|
||||
|
||||
def init_connection_state(self):
|
||||
"""Initialize the database connection settings."""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require an init_connection_state() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an init_connection_state() method')
|
||||
|
||||
def create_cursor(self, name=None):
|
||||
"""Create a cursor. Assume that a connection is established."""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require a create_cursor() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a create_cursor() method')
|
||||
|
||||
# ##### Backend-specific methods for creating connections #####
|
||||
|
||||
@@ -216,21 +191,21 @@ class BaseDatabaseWrapper:
|
||||
self.savepoint_ids = []
|
||||
self.needs_rollback = False
|
||||
# Reset parameters defining when to close the connection
|
||||
max_age = self.settings_dict["CONN_MAX_AGE"]
|
||||
max_age = self.settings_dict['CONN_MAX_AGE']
|
||||
self.close_at = None if max_age is None else time.monotonic() + max_age
|
||||
self.closed_in_transaction = False
|
||||
self.errors_occurred = False
|
||||
# Establish the connection
|
||||
conn_params = self.get_connection_params()
|
||||
self.connection = self.get_new_connection(conn_params)
|
||||
self.set_autocommit(self.settings_dict["AUTOCOMMIT"])
|
||||
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
|
||||
self.init_connection_state()
|
||||
connection_created.send(sender=self.__class__, connection=self)
|
||||
|
||||
self.run_on_commit = []
|
||||
|
||||
def check_settings(self):
|
||||
if self.settings_dict["TIME_ZONE"] is not None and not settings.USE_TZ:
|
||||
if self.settings_dict['TIME_ZONE'] is not None and not settings.USE_TZ:
|
||||
raise ImproperlyConfigured(
|
||||
"Connection '%s' cannot set TIME_ZONE because USE_TZ is False."
|
||||
% self.alias
|
||||
@@ -355,7 +330,7 @@ class BaseDatabaseWrapper:
|
||||
return
|
||||
|
||||
thread_ident = _thread.get_ident()
|
||||
tid = str(thread_ident).replace("-", "")
|
||||
tid = str(thread_ident).replace('-', '')
|
||||
|
||||
self.savepoint_state += 1
|
||||
sid = "s%s_x%d" % (tid, self.savepoint_state)
|
||||
@@ -405,9 +380,7 @@ class BaseDatabaseWrapper:
|
||||
"""
|
||||
Backend-specific implementation to enable or disable autocommit.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require a _set_autocommit() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _set_autocommit() method')
|
||||
|
||||
# ##### Generic transaction management methods #####
|
||||
|
||||
@@ -416,9 +389,7 @@ class BaseDatabaseWrapper:
|
||||
self.ensure_connection()
|
||||
return self.autocommit
|
||||
|
||||
def set_autocommit(
|
||||
self, autocommit, force_begin_transaction_with_broken_autocommit=False
|
||||
):
|
||||
def set_autocommit(self, autocommit, force_begin_transaction_with_broken_autocommit=False):
|
||||
"""
|
||||
Enable or disable autocommit.
|
||||
|
||||
@@ -434,9 +405,8 @@ class BaseDatabaseWrapper:
|
||||
self.ensure_connection()
|
||||
|
||||
start_transaction_under_autocommit = (
|
||||
force_begin_transaction_with_broken_autocommit
|
||||
and not autocommit
|
||||
and hasattr(self, "_start_transaction_under_autocommit")
|
||||
force_begin_transaction_with_broken_autocommit and not autocommit and
|
||||
hasattr(self, '_start_transaction_under_autocommit')
|
||||
)
|
||||
|
||||
if start_transaction_under_autocommit:
|
||||
@@ -454,8 +424,7 @@ class BaseDatabaseWrapper:
|
||||
"""Get the "needs rollback" flag -- for *advanced use* only."""
|
||||
if not self.in_atomic_block:
|
||||
raise TransactionManagementError(
|
||||
"The rollback flag doesn't work outside of an 'atomic' block."
|
||||
)
|
||||
"The rollback flag doesn't work outside of an 'atomic' block.")
|
||||
return self.needs_rollback
|
||||
|
||||
def set_rollback(self, rollback):
|
||||
@@ -464,23 +433,20 @@ class BaseDatabaseWrapper:
|
||||
"""
|
||||
if not self.in_atomic_block:
|
||||
raise TransactionManagementError(
|
||||
"The rollback flag doesn't work outside of an 'atomic' block."
|
||||
)
|
||||
"The rollback flag doesn't work outside of an 'atomic' block.")
|
||||
self.needs_rollback = rollback
|
||||
|
||||
def validate_no_atomic_block(self):
|
||||
"""Raise an error if an atomic block is active."""
|
||||
if self.in_atomic_block:
|
||||
raise TransactionManagementError(
|
||||
"This is forbidden when an 'atomic' block is active."
|
||||
)
|
||||
"This is forbidden when an 'atomic' block is active.")
|
||||
|
||||
def validate_no_broken_transaction(self):
|
||||
if self.needs_rollback:
|
||||
raise TransactionManagementError(
|
||||
"An error occurred in the current transaction. You can't "
|
||||
"execute queries until the end of the 'atomic' block."
|
||||
)
|
||||
"execute queries until the end of the 'atomic' block.")
|
||||
|
||||
# ##### Foreign key constraints checks handling #####
|
||||
|
||||
@@ -531,8 +497,7 @@ class BaseDatabaseWrapper:
|
||||
as that may prevent Django from recycling unusable connections.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseWrapper may require an is_usable() method"
|
||||
)
|
||||
"subclasses of BaseDatabaseWrapper may require an is_usable() method")
|
||||
|
||||
def close_if_unusable_or_obsolete(self):
|
||||
"""
|
||||
@@ -542,7 +507,7 @@ class BaseDatabaseWrapper:
|
||||
if self.connection is not None:
|
||||
# If the application didn't restore the original autocommit setting,
|
||||
# don't take chances, drop the connection.
|
||||
if self.get_autocommit() != self.settings_dict["AUTOCOMMIT"]:
|
||||
if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:
|
||||
self.close()
|
||||
return
|
||||
|
||||
@@ -573,9 +538,7 @@ class BaseDatabaseWrapper:
|
||||
def dec_thread_sharing(self):
|
||||
with self._thread_sharing_lock:
|
||||
if self._thread_sharing_count <= 0:
|
||||
raise RuntimeError(
|
||||
"Cannot decrement the thread sharing count below zero."
|
||||
)
|
||||
raise RuntimeError('Cannot decrement the thread sharing count below zero.')
|
||||
self._thread_sharing_count -= 1
|
||||
|
||||
def validate_thread_sharing(self):
|
||||
@@ -590,7 +553,8 @@ class BaseDatabaseWrapper:
|
||||
"DatabaseWrapper objects created in a "
|
||||
"thread can only be used in that same thread. The object "
|
||||
"with alias '%s' was created in thread id %s and this is "
|
||||
"thread id %s." % (self.alias, self._thread_ident, _thread.get_ident())
|
||||
"thread id %s."
|
||||
% (self.alias, self._thread_ident, _thread.get_ident())
|
||||
)
|
||||
|
||||
# ##### Miscellaneous #####
|
||||
@@ -651,7 +615,7 @@ class BaseDatabaseWrapper:
|
||||
being exposed to potential child threads while (or after) the test
|
||||
database is destroyed. Refs #10868, #17786, #16969.
|
||||
"""
|
||||
conn = self.__class__({**self.settings_dict, "NAME": None}, alias=NO_DB_ALIAS)
|
||||
conn = self.__class__({**self.settings_dict, 'NAME': None}, alias=NO_DB_ALIAS)
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
yield cursor
|
||||
@@ -664,8 +628,7 @@ class BaseDatabaseWrapper:
|
||||
"""
|
||||
if self.SchemaEditorClass is None:
|
||||
raise NotImplementedError(
|
||||
"The SchemaEditorClass attribute of this database wrapper is still None"
|
||||
)
|
||||
'The SchemaEditorClass attribute of this database wrapper is still None')
|
||||
return self.SchemaEditorClass(self, *args, **kwargs)
|
||||
|
||||
def on_commit(self, func):
|
||||
@@ -675,9 +638,7 @@ class BaseDatabaseWrapper:
|
||||
# Transaction in progress; save for execution on commit.
|
||||
self.run_on_commit.append((set(self.savepoint_ids), func))
|
||||
elif not self.get_autocommit():
|
||||
raise TransactionManagementError(
|
||||
"on_commit() cannot be used in manual transaction management"
|
||||
)
|
||||
raise TransactionManagementError('on_commit() cannot be used in manual transaction management')
|
||||
else:
|
||||
# No transaction in progress and in autocommit mode; execute
|
||||
# immediately.
|
||||
|
||||
@@ -4,7 +4,6 @@ import subprocess
|
||||
|
||||
class BaseDatabaseClient:
|
||||
"""Encapsulate backend-specific methods for opening a client shell."""
|
||||
|
||||
# This should be a string representing the name of the executable
|
||||
# (e.g., "psql"). Subclasses must override this.
|
||||
executable_name = None
|
||||
@@ -16,13 +15,11 @@ class BaseDatabaseClient:
|
||||
@classmethod
|
||||
def settings_to_cmd_args_env(cls, settings_dict, parameters):
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseClient must provide a "
|
||||
"settings_to_cmd_args_env() method or override a runshell()."
|
||||
'subclasses of BaseDatabaseClient must provide a '
|
||||
'settings_to_cmd_args_env() method or override a runshell().'
|
||||
)
|
||||
|
||||
def runshell(self, parameters):
|
||||
args, env = self.settings_to_cmd_args_env(
|
||||
self.connection.settings_dict, parameters
|
||||
)
|
||||
args, env = self.settings_to_cmd_args_env(self.connection.settings_dict, parameters)
|
||||
env = {**os.environ, **env} if env else None
|
||||
subprocess.run(args, env=env, check=True)
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.utils.module_loading import import_string
|
||||
|
||||
# The prefix to put on the default database name when creating
|
||||
# the test database.
|
||||
TEST_DATABASE_PREFIX = "test_"
|
||||
TEST_DATABASE_PREFIX = 'test_'
|
||||
|
||||
|
||||
class BaseDatabaseCreation:
|
||||
@@ -20,7 +20,6 @@ class BaseDatabaseCreation:
|
||||
Encapsulate backend-specific differences pertaining to creation and
|
||||
destruction of the test database.
|
||||
"""
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
@@ -30,9 +29,7 @@ class BaseDatabaseCreation:
|
||||
def log(self, msg):
|
||||
sys.stderr.write(msg + os.linesep)
|
||||
|
||||
def create_test_db(
|
||||
self, verbosity=1, autoclobber=False, serialize=True, keepdb=False
|
||||
):
|
||||
def create_test_db(self, verbosity=1, autoclobber=False, serialize=True, keepdb=False):
|
||||
"""
|
||||
Create a test database, prompting the user for confirmation if the
|
||||
database already exists. Return the name of the test database created.
|
||||
@@ -43,17 +40,14 @@ class BaseDatabaseCreation:
|
||||
test_database_name = self._get_test_db_name()
|
||||
|
||||
if verbosity >= 1:
|
||||
action = "Creating"
|
||||
action = 'Creating'
|
||||
if keepdb:
|
||||
action = "Using existing"
|
||||
|
||||
self.log(
|
||||
"%s test database for alias %s..."
|
||||
% (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, test_database_name),
|
||||
)
|
||||
)
|
||||
self.log('%s test database for alias %s...' % (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, test_database_name),
|
||||
))
|
||||
|
||||
# We could skip this call if keepdb is True, but we instead
|
||||
# give it the keepdb param. This is to handle the case
|
||||
@@ -67,24 +61,25 @@ class BaseDatabaseCreation:
|
||||
self.connection.settings_dict["NAME"] = test_database_name
|
||||
|
||||
try:
|
||||
if self.connection.settings_dict["TEST"]["MIGRATE"] is False:
|
||||
if self.connection.settings_dict['TEST']['MIGRATE'] is False:
|
||||
# Disable migrations for all apps.
|
||||
old_migration_modules = settings.MIGRATION_MODULES
|
||||
settings.MIGRATION_MODULES = {
|
||||
app.label: None for app in apps.get_app_configs()
|
||||
app.label: None
|
||||
for app in apps.get_app_configs()
|
||||
}
|
||||
# We report migrate messages at one level lower than that
|
||||
# requested. This ensures we don't get flooded with messages during
|
||||
# testing (unless you really ask to be flooded).
|
||||
call_command(
|
||||
"migrate",
|
||||
'migrate',
|
||||
verbosity=max(verbosity - 1, 0),
|
||||
interactive=False,
|
||||
database=self.connection.alias,
|
||||
run_syncdb=True,
|
||||
)
|
||||
finally:
|
||||
if self.connection.settings_dict["TEST"]["MIGRATE"] is False:
|
||||
if self.connection.settings_dict['TEST']['MIGRATE'] is False:
|
||||
settings.MIGRATION_MODULES = old_migration_modules
|
||||
|
||||
# We then serialize the current state of the database into a string
|
||||
@@ -94,12 +89,12 @@ class BaseDatabaseCreation:
|
||||
if serialize:
|
||||
self.connection._test_serialized_contents = self.serialize_db_to_string()
|
||||
|
||||
call_command("createcachetable", database=self.connection.alias)
|
||||
call_command('createcachetable', database=self.connection.alias)
|
||||
|
||||
# Ensure a connection for the side effect of initializing the test database.
|
||||
self.connection.ensure_connection()
|
||||
|
||||
if os.environ.get("RUNNING_DJANGOS_TEST_SUITE") == "true":
|
||||
if os.environ.get('RUNNING_DJANGOS_TEST_SUITE') == 'true':
|
||||
self.mark_expected_failures_and_skips()
|
||||
|
||||
return test_database_name
|
||||
@@ -109,7 +104,7 @@ class BaseDatabaseCreation:
|
||||
Set this database up to be used in testing as a mirror of a primary
|
||||
database whose settings are given.
|
||||
"""
|
||||
self.connection.settings_dict["NAME"] = primary_settings_dict["NAME"]
|
||||
self.connection.settings_dict['NAME'] = primary_settings_dict['NAME']
|
||||
|
||||
def serialize_db_to_string(self):
|
||||
"""
|
||||
@@ -120,23 +115,22 @@ class BaseDatabaseCreation:
|
||||
# Iteratively return every object for all models to serialize.
|
||||
def get_objects():
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
|
||||
loader = MigrationLoader(self.connection)
|
||||
for app_config in apps.get_app_configs():
|
||||
if (
|
||||
app_config.models_module is not None
|
||||
and app_config.label in loader.migrated_apps
|
||||
and app_config.name not in settings.TEST_NON_SERIALIZED_APPS
|
||||
app_config.models_module is not None and
|
||||
app_config.label in loader.migrated_apps and
|
||||
app_config.name not in settings.TEST_NON_SERIALIZED_APPS
|
||||
):
|
||||
for model in app_config.get_models():
|
||||
if model._meta.can_migrate(
|
||||
self.connection
|
||||
) and router.allow_migrate_model(self.connection.alias, model):
|
||||
if (
|
||||
model._meta.can_migrate(self.connection) and
|
||||
router.allow_migrate_model(self.connection.alias, model)
|
||||
):
|
||||
queryset = model._base_manager.using(
|
||||
self.connection.alias,
|
||||
).order_by(model._meta.pk.name)
|
||||
yield from queryset.iterator()
|
||||
|
||||
# Serialize to a string
|
||||
out = StringIO()
|
||||
serializers.serialize("json", get_objects(), indent=None, stream=out)
|
||||
@@ -154,9 +148,7 @@ class BaseDatabaseCreation:
|
||||
# Disable constraint checks, because some databases (MySQL) doesn't
|
||||
# support deferred checks.
|
||||
with self.connection.constraint_checks_disabled():
|
||||
for obj in serializers.deserialize(
|
||||
"json", data, using=self.connection.alias
|
||||
):
|
||||
for obj in serializers.deserialize('json', data, using=self.connection.alias):
|
||||
obj.save()
|
||||
table_names.add(obj.object.__class__._meta.db_table)
|
||||
# Manually check for any invalid keys that might have been added,
|
||||
@@ -169,7 +161,7 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
return "'%s'%s" % (
|
||||
self.connection.alias,
|
||||
(" ('%s')" % database_name) if verbosity >= 2 else "",
|
||||
(" ('%s')" % database_name) if verbosity >= 2 else '',
|
||||
)
|
||||
|
||||
def _get_test_db_name(self):
|
||||
@@ -179,12 +171,12 @@ class BaseDatabaseCreation:
|
||||
_create_test_db() and when no external munging is done with the 'NAME'
|
||||
settings.
|
||||
"""
|
||||
if self.connection.settings_dict["TEST"]["NAME"]:
|
||||
return self.connection.settings_dict["TEST"]["NAME"]
|
||||
return TEST_DATABASE_PREFIX + self.connection.settings_dict["NAME"]
|
||||
if self.connection.settings_dict['TEST']['NAME']:
|
||||
return self.connection.settings_dict['TEST']['NAME']
|
||||
return TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
|
||||
|
||||
def _execute_create_test_db(self, cursor, parameters, keepdb=False):
|
||||
cursor.execute("CREATE DATABASE %(dbname)s %(suffix)s" % parameters)
|
||||
cursor.execute('CREATE DATABASE %(dbname)s %(suffix)s' % parameters)
|
||||
|
||||
def _create_test_db(self, verbosity, autoclobber, keepdb=False):
|
||||
"""
|
||||
@@ -192,8 +184,8 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
test_database_name = self._get_test_db_name()
|
||||
test_db_params = {
|
||||
"dbname": self.connection.ops.quote_name(test_database_name),
|
||||
"suffix": self.sql_table_creation_suffix(),
|
||||
'dbname': self.connection.ops.quote_name(test_database_name),
|
||||
'suffix': self.sql_table_creation_suffix(),
|
||||
}
|
||||
# Create the test database and connect to it.
|
||||
with self._nodb_cursor() as cursor:
|
||||
@@ -205,30 +197,24 @@ class BaseDatabaseCreation:
|
||||
if keepdb:
|
||||
return test_database_name
|
||||
|
||||
self.log("Got an error creating the test database: %s" % e)
|
||||
self.log('Got an error creating the test database: %s' % e)
|
||||
if not autoclobber:
|
||||
confirm = input(
|
||||
"Type 'yes' if you would like to try deleting the test "
|
||||
"database '%s', or 'no' to cancel: " % test_database_name
|
||||
)
|
||||
if autoclobber or confirm == "yes":
|
||||
"database '%s', or 'no' to cancel: " % test_database_name)
|
||||
if autoclobber or confirm == 'yes':
|
||||
try:
|
||||
if verbosity >= 1:
|
||||
self.log(
|
||||
"Destroying old test database for alias %s..."
|
||||
% (
|
||||
self._get_database_display_str(
|
||||
verbosity, test_database_name
|
||||
),
|
||||
)
|
||||
)
|
||||
cursor.execute("DROP DATABASE %(dbname)s" % test_db_params)
|
||||
self.log('Destroying old test database for alias %s...' % (
|
||||
self._get_database_display_str(verbosity, test_database_name),
|
||||
))
|
||||
cursor.execute('DROP DATABASE %(dbname)s' % test_db_params)
|
||||
self._execute_create_test_db(cursor, test_db_params, keepdb)
|
||||
except Exception as e:
|
||||
self.log("Got an error recreating the test database: %s" % e)
|
||||
self.log('Got an error recreating the test database: %s' % e)
|
||||
sys.exit(2)
|
||||
else:
|
||||
self.log("Tests cancelled.")
|
||||
self.log('Tests cancelled.')
|
||||
sys.exit(1)
|
||||
|
||||
return test_database_name
|
||||
@@ -237,19 +223,16 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
Clone a test database.
|
||||
"""
|
||||
source_database_name = self.connection.settings_dict["NAME"]
|
||||
source_database_name = self.connection.settings_dict['NAME']
|
||||
|
||||
if verbosity >= 1:
|
||||
action = "Cloning test database"
|
||||
action = 'Cloning test database'
|
||||
if keepdb:
|
||||
action = "Using existing clone"
|
||||
self.log(
|
||||
"%s for alias %s..."
|
||||
% (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, source_database_name),
|
||||
)
|
||||
)
|
||||
action = 'Using existing clone'
|
||||
self.log('%s for alias %s...' % (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, source_database_name),
|
||||
))
|
||||
|
||||
# We could skip this call if keepdb is True, but we instead
|
||||
# give it the keepdb param. See create_test_db for details.
|
||||
@@ -263,10 +246,7 @@ class BaseDatabaseCreation:
|
||||
# already and its name has been copied to settings_dict['NAME'] so
|
||||
# we don't need to call _get_test_db_name.
|
||||
orig_settings_dict = self.connection.settings_dict
|
||||
return {
|
||||
**orig_settings_dict,
|
||||
"NAME": "{}_{}".format(orig_settings_dict["NAME"], suffix),
|
||||
}
|
||||
return {**orig_settings_dict, 'NAME': '{}_{}'.format(orig_settings_dict['NAME'], suffix)}
|
||||
|
||||
def _clone_test_db(self, suffix, verbosity, keepdb=False):
|
||||
"""
|
||||
@@ -274,33 +254,27 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"The database backend doesn't support cloning databases. "
|
||||
"Disable the option to run tests in parallel processes."
|
||||
)
|
||||
"Disable the option to run tests in parallel processes.")
|
||||
|
||||
def destroy_test_db(
|
||||
self, old_database_name=None, verbosity=1, keepdb=False, suffix=None
|
||||
):
|
||||
def destroy_test_db(self, old_database_name=None, verbosity=1, keepdb=False, suffix=None):
|
||||
"""
|
||||
Destroy a test database, prompting the user for confirmation if the
|
||||
database already exists.
|
||||
"""
|
||||
self.connection.close()
|
||||
if suffix is None:
|
||||
test_database_name = self.connection.settings_dict["NAME"]
|
||||
test_database_name = self.connection.settings_dict['NAME']
|
||||
else:
|
||||
test_database_name = self.get_test_db_clone_settings(suffix)["NAME"]
|
||||
test_database_name = self.get_test_db_clone_settings(suffix)['NAME']
|
||||
|
||||
if verbosity >= 1:
|
||||
action = "Destroying"
|
||||
action = 'Destroying'
|
||||
if keepdb:
|
||||
action = "Preserving"
|
||||
self.log(
|
||||
"%s test database for alias %s..."
|
||||
% (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, test_database_name),
|
||||
)
|
||||
)
|
||||
action = 'Preserving'
|
||||
self.log('%s test database for alias %s...' % (
|
||||
action,
|
||||
self._get_database_display_str(verbosity, test_database_name),
|
||||
))
|
||||
|
||||
# if we want to preserve the database
|
||||
# skip the actual destroying piece.
|
||||
@@ -321,9 +295,8 @@ class BaseDatabaseCreation:
|
||||
# to do so, because it's not allowed to delete a database while being
|
||||
# connected to it.
|
||||
with self._nodb_cursor() as cursor:
|
||||
cursor.execute(
|
||||
"DROP DATABASE %s" % self.connection.ops.quote_name(test_database_name)
|
||||
)
|
||||
cursor.execute("DROP DATABASE %s"
|
||||
% self.connection.ops.quote_name(test_database_name))
|
||||
|
||||
def mark_expected_failures_and_skips(self):
|
||||
"""
|
||||
@@ -331,8 +304,8 @@ class BaseDatabaseCreation:
|
||||
database and test which should be skipped on this database.
|
||||
"""
|
||||
for test_name in self.connection.features.django_test_expected_failures:
|
||||
test_case_name, _, test_method_name = test_name.rpartition(".")
|
||||
test_app = test_name.split(".")[0]
|
||||
test_case_name, _, test_method_name = test_name.rpartition('.')
|
||||
test_app = test_name.split('.')[0]
|
||||
# Importing a test app that isn't installed raises RuntimeError.
|
||||
if test_app in settings.INSTALLED_APPS:
|
||||
test_case = import_string(test_case_name)
|
||||
@@ -340,8 +313,8 @@ class BaseDatabaseCreation:
|
||||
setattr(test_case, test_method_name, expectedFailure(test_method))
|
||||
for reason, tests in self.connection.features.django_test_skips.items():
|
||||
for test_name in tests:
|
||||
test_case_name, _, test_method_name = test_name.rpartition(".")
|
||||
test_app = test_name.split(".")[0]
|
||||
test_case_name, _, test_method_name = test_name.rpartition('.')
|
||||
test_app = test_name.split('.')[0]
|
||||
# Importing a test app that isn't installed raises RuntimeError.
|
||||
if test_app in settings.INSTALLED_APPS:
|
||||
test_case = import_string(test_case_name)
|
||||
@@ -352,7 +325,7 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
SQL to append to the end of the test table creation statements.
|
||||
"""
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def test_db_signature(self):
|
||||
"""
|
||||
@@ -362,8 +335,8 @@ class BaseDatabaseCreation:
|
||||
"""
|
||||
settings_dict = self.connection.settings_dict
|
||||
return (
|
||||
settings_dict["HOST"],
|
||||
settings_dict["PORT"],
|
||||
settings_dict["ENGINE"],
|
||||
settings_dict['HOST'],
|
||||
settings_dict['PORT'],
|
||||
settings_dict['ENGINE'],
|
||||
self._get_test_db_name(),
|
||||
)
|
||||
|
||||
@@ -64,9 +64,6 @@ class BaseDatabaseFeatures:
|
||||
has_real_datatype = False
|
||||
supports_subqueries_in_group_by = True
|
||||
|
||||
# Does the backend ignore unnecessary ORDER BY clauses in subqueries?
|
||||
ignores_unnecessary_order_by_in_subqueries = True
|
||||
|
||||
# Is there a true datatype for uuid?
|
||||
has_native_uuid_field = False
|
||||
|
||||
@@ -133,21 +130,21 @@ class BaseDatabaseFeatures:
|
||||
# Map fields which some backends may not be able to differentiate to the
|
||||
# field it's introspected as.
|
||||
introspected_field_types = {
|
||||
"AutoField": "AutoField",
|
||||
"BigAutoField": "BigAutoField",
|
||||
"BigIntegerField": "BigIntegerField",
|
||||
"BinaryField": "BinaryField",
|
||||
"BooleanField": "BooleanField",
|
||||
"CharField": "CharField",
|
||||
"DurationField": "DurationField",
|
||||
"GenericIPAddressField": "GenericIPAddressField",
|
||||
"IntegerField": "IntegerField",
|
||||
"PositiveBigIntegerField": "PositiveBigIntegerField",
|
||||
"PositiveIntegerField": "PositiveIntegerField",
|
||||
"PositiveSmallIntegerField": "PositiveSmallIntegerField",
|
||||
"SmallAutoField": "SmallAutoField",
|
||||
"SmallIntegerField": "SmallIntegerField",
|
||||
"TimeField": "TimeField",
|
||||
'AutoField': 'AutoField',
|
||||
'BigAutoField': 'BigAutoField',
|
||||
'BigIntegerField': 'BigIntegerField',
|
||||
'BinaryField': 'BinaryField',
|
||||
'BooleanField': 'BooleanField',
|
||||
'CharField': 'CharField',
|
||||
'DurationField': 'DurationField',
|
||||
'GenericIPAddressField': 'GenericIPAddressField',
|
||||
'IntegerField': 'IntegerField',
|
||||
'PositiveBigIntegerField': 'PositiveBigIntegerField',
|
||||
'PositiveIntegerField': 'PositiveIntegerField',
|
||||
'PositiveSmallIntegerField': 'PositiveSmallIntegerField',
|
||||
'SmallAutoField': 'SmallAutoField',
|
||||
'SmallIntegerField': 'SmallIntegerField',
|
||||
'TimeField': 'TimeField',
|
||||
}
|
||||
|
||||
# Can the backend introspect the column order (ASC/DESC) for indexes?
|
||||
@@ -204,7 +201,7 @@ class BaseDatabaseFeatures:
|
||||
has_case_insensitive_like = True
|
||||
|
||||
# Suffix for backends that don't support "SELECT xxx;" queries.
|
||||
bare_select_suffix = ""
|
||||
bare_select_suffix = ''
|
||||
|
||||
# If NULL is implied on columns without needing to be explicitly specified
|
||||
implied_column_null = False
|
||||
@@ -324,13 +321,11 @@ class BaseDatabaseFeatures:
|
||||
|
||||
# Collation names for use by the Django test suite.
|
||||
test_collations = {
|
||||
"ci": None, # Case-insensitive.
|
||||
"cs": None, # Case-sensitive.
|
||||
"non_default": None, # Non-default.
|
||||
"swedish_ci": None, # Swedish case-insensitive.
|
||||
'ci': None, # Case-insensitive.
|
||||
'cs': None, # Case-sensitive.
|
||||
'non_default': None, # Non-default.
|
||||
'swedish_ci': None # Swedish case-insensitive.
|
||||
}
|
||||
# SQL template override for tests.aggregation.tests.NowUTC
|
||||
test_now_utc_template = None
|
||||
|
||||
# A set of dotted paths to tests in Django's test suite that are expected
|
||||
# to fail on this database.
|
||||
@@ -351,14 +346,14 @@ class BaseDatabaseFeatures:
|
||||
def supports_transactions(self):
|
||||
"""Confirm support for transactions."""
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("CREATE TABLE ROLLBACK_TEST (X INT)")
|
||||
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
|
||||
self.connection.set_autocommit(False)
|
||||
cursor.execute("INSERT INTO ROLLBACK_TEST (X) VALUES (8)")
|
||||
cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
|
||||
self.connection.rollback()
|
||||
self.connection.set_autocommit(True)
|
||||
cursor.execute("SELECT COUNT(X) FROM ROLLBACK_TEST")
|
||||
(count,) = cursor.fetchone()
|
||||
cursor.execute("DROP TABLE ROLLBACK_TEST")
|
||||
cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
|
||||
count, = cursor.fetchone()
|
||||
cursor.execute('DROP TABLE ROLLBACK_TEST')
|
||||
return count == 0
|
||||
|
||||
def allows_group_by_selected_pks_on_model(self, model):
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
from collections import namedtuple
|
||||
|
||||
# Structure returned by DatabaseIntrospection.get_table_list()
|
||||
TableInfo = namedtuple("TableInfo", ["name", "type"])
|
||||
TableInfo = namedtuple('TableInfo', ['name', 'type'])
|
||||
|
||||
# Structure returned by the DB-API cursor.description interface (PEP 249)
|
||||
FieldInfo = namedtuple(
|
||||
"FieldInfo",
|
||||
"name type_code display_size internal_size precision scale null_ok "
|
||||
"default collation",
|
||||
'FieldInfo',
|
||||
'name type_code display_size internal_size precision scale null_ok '
|
||||
'default collation'
|
||||
)
|
||||
|
||||
|
||||
class BaseDatabaseIntrospection:
|
||||
"""Encapsulate backend-specific introspection utilities."""
|
||||
|
||||
data_types_reverse = {}
|
||||
|
||||
def __init__(self, connection):
|
||||
@@ -44,14 +43,9 @@ class BaseDatabaseIntrospection:
|
||||
the database's ORDER BY here to avoid subtle differences in sorting
|
||||
order between databases.
|
||||
"""
|
||||
|
||||
def get_names(cursor):
|
||||
return sorted(
|
||||
ti.name
|
||||
for ti in self.get_table_list(cursor)
|
||||
if include_views or ti.type == "t"
|
||||
)
|
||||
|
||||
return sorted(ti.name for ti in self.get_table_list(cursor)
|
||||
if include_views or ti.type == 't')
|
||||
if cursor is None:
|
||||
with self.connection.cursor() as cursor:
|
||||
return get_names(cursor)
|
||||
@@ -62,10 +56,7 @@ class BaseDatabaseIntrospection:
|
||||
Return an unsorted list of TableInfo named tuples of all tables and
|
||||
views that exist in the database.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a get_table_list() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
|
||||
|
||||
def get_table_description(self, cursor, table_name):
|
||||
"""
|
||||
@@ -73,14 +64,13 @@ class BaseDatabaseIntrospection:
|
||||
interface.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a "
|
||||
"get_table_description() method."
|
||||
'subclasses of BaseDatabaseIntrospection may require a '
|
||||
'get_table_description() method.'
|
||||
)
|
||||
|
||||
def get_migratable_models(self):
|
||||
from django.apps import apps
|
||||
from django.db import router
|
||||
|
||||
return (
|
||||
model
|
||||
for app_config in apps.get_app_configs()
|
||||
@@ -101,15 +91,16 @@ class BaseDatabaseIntrospection:
|
||||
continue
|
||||
tables.add(model._meta.db_table)
|
||||
tables.update(
|
||||
f.m2m_db_table()
|
||||
for f in model._meta.local_many_to_many
|
||||
f.m2m_db_table() for f in model._meta.local_many_to_many
|
||||
if f.remote_field.through._meta.managed
|
||||
)
|
||||
tables = list(tables)
|
||||
if only_existing:
|
||||
existing_tables = set(self.table_names(include_views=include_views))
|
||||
tables = [
|
||||
t for t in tables if self.identifier_converter(t) in existing_tables
|
||||
t
|
||||
for t in tables
|
||||
if self.identifier_converter(t) in existing_tables
|
||||
]
|
||||
return tables
|
||||
|
||||
@@ -120,8 +111,7 @@ class BaseDatabaseIntrospection:
|
||||
"""
|
||||
tables = set(map(self.identifier_converter, tables))
|
||||
return {
|
||||
m
|
||||
for m in self.get_migratable_models()
|
||||
m for m in self.get_migratable_models()
|
||||
if self.identifier_converter(m._meta.db_table) in tables
|
||||
}
|
||||
|
||||
@@ -137,19 +127,13 @@ class BaseDatabaseIntrospection:
|
||||
continue
|
||||
if model._meta.swapped:
|
||||
continue
|
||||
sequence_list.extend(
|
||||
self.get_sequences(
|
||||
cursor, model._meta.db_table, model._meta.local_fields
|
||||
)
|
||||
)
|
||||
sequence_list.extend(self.get_sequences(cursor, model._meta.db_table, model._meta.local_fields))
|
||||
for f in model._meta.local_many_to_many:
|
||||
# If this is an m2m using an intermediate table,
|
||||
# we don't need to reset the sequence.
|
||||
if f.remote_field.through._meta.auto_created:
|
||||
sequence = self.get_sequences(cursor, f.m2m_db_table())
|
||||
sequence_list.extend(
|
||||
sequence or [{"table": f.m2m_db_table(), "column": None}]
|
||||
)
|
||||
sequence_list.extend(sequence or [{'table': f.m2m_db_table(), 'column': None}])
|
||||
return sequence_list
|
||||
|
||||
def get_sequences(self, cursor, table_name, table_fields=()):
|
||||
@@ -158,10 +142,7 @@ class BaseDatabaseIntrospection:
|
||||
is a dict: {'table': <table_name>, 'column': <column_name>}. An optional
|
||||
'name' key can be added if the backend supports named sequences.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a get_sequences() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_sequences() method')
|
||||
|
||||
def get_relations(self, cursor, table_name):
|
||||
"""
|
||||
@@ -170,8 +151,8 @@ class BaseDatabaseIntrospection:
|
||||
relationships to the given table.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a "
|
||||
"get_relations() method."
|
||||
'subclasses of BaseDatabaseIntrospection may require a '
|
||||
'get_relations() method.'
|
||||
)
|
||||
|
||||
def get_key_columns(self, cursor, table_name):
|
||||
@@ -180,18 +161,15 @@ class BaseDatabaseIntrospection:
|
||||
(column_name, referenced_table_name, referenced_column_name)
|
||||
for all key columns in given table.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a get_key_columns() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_key_columns() method')
|
||||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Return the name of the primary key column for the given table.
|
||||
"""
|
||||
for constraint in self.get_constraints(cursor, table_name).values():
|
||||
if constraint["primary_key"]:
|
||||
return constraint["columns"][0]
|
||||
if constraint['primary_key']:
|
||||
return constraint['columns'][0]
|
||||
return None
|
||||
|
||||
def get_constraints(self, cursor, table_name):
|
||||
@@ -213,7 +191,4 @@ class BaseDatabaseIntrospection:
|
||||
Some backends may return special constraint names that don't exist
|
||||
if they don't name constraints of a certain type (e.g. SQLite)
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseIntrospection may require a get_constraints() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_constraints() method')
|
||||
|
||||
@@ -16,26 +16,25 @@ class BaseDatabaseOperations:
|
||||
Encapsulate backend-specific differences, such as the way a backend
|
||||
performs ordering or calculates the ID of a recently-inserted row.
|
||||
"""
|
||||
|
||||
compiler_module = "django.db.models.sql.compiler"
|
||||
|
||||
# Integer field safe ranges by `internal_type` as documented
|
||||
# in docs/ref/models/fields.txt.
|
||||
integer_field_ranges = {
|
||||
"SmallIntegerField": (-32768, 32767),
|
||||
"IntegerField": (-2147483648, 2147483647),
|
||||
"BigIntegerField": (-9223372036854775808, 9223372036854775807),
|
||||
"PositiveBigIntegerField": (0, 9223372036854775807),
|
||||
"PositiveSmallIntegerField": (0, 32767),
|
||||
"PositiveIntegerField": (0, 2147483647),
|
||||
"SmallAutoField": (-32768, 32767),
|
||||
"AutoField": (-2147483648, 2147483647),
|
||||
"BigAutoField": (-9223372036854775808, 9223372036854775807),
|
||||
'SmallIntegerField': (-32768, 32767),
|
||||
'IntegerField': (-2147483648, 2147483647),
|
||||
'BigIntegerField': (-9223372036854775808, 9223372036854775807),
|
||||
'PositiveBigIntegerField': (0, 9223372036854775807),
|
||||
'PositiveSmallIntegerField': (0, 32767),
|
||||
'PositiveIntegerField': (0, 2147483647),
|
||||
'SmallAutoField': (-32768, 32767),
|
||||
'AutoField': (-2147483648, 2147483647),
|
||||
'BigAutoField': (-9223372036854775808, 9223372036854775807),
|
||||
}
|
||||
set_operators = {
|
||||
"union": "UNION",
|
||||
"intersection": "INTERSECT",
|
||||
"difference": "EXCEPT",
|
||||
'union': 'UNION',
|
||||
'intersection': 'INTERSECT',
|
||||
'difference': 'EXCEPT',
|
||||
}
|
||||
# Mapping of Field.get_internal_type() (typically the model field's class
|
||||
# name) to the data type to use for the Cast() function, if different from
|
||||
@@ -45,11 +44,11 @@ class BaseDatabaseOperations:
|
||||
cast_char_field_without_max_length = None
|
||||
|
||||
# Start and end points for window expressions.
|
||||
PRECEDING = "PRECEDING"
|
||||
FOLLOWING = "FOLLOWING"
|
||||
UNBOUNDED_PRECEDING = "UNBOUNDED " + PRECEDING
|
||||
UNBOUNDED_FOLLOWING = "UNBOUNDED " + FOLLOWING
|
||||
CURRENT_ROW = "CURRENT ROW"
|
||||
PRECEDING = 'PRECEDING'
|
||||
FOLLOWING = 'FOLLOWING'
|
||||
UNBOUNDED_PRECEDING = 'UNBOUNDED ' + PRECEDING
|
||||
UNBOUNDED_FOLLOWING = 'UNBOUNDED ' + FOLLOWING
|
||||
CURRENT_ROW = 'CURRENT ROW'
|
||||
|
||||
# Prefix for EXPLAIN queries, or None EXPLAIN isn't supported.
|
||||
explain_prefix = None
|
||||
@@ -91,17 +90,14 @@ class BaseDatabaseOperations:
|
||||
to that type. The resulting string should contain a '%s' placeholder
|
||||
for the expression being cast.
|
||||
"""
|
||||
return "%s"
|
||||
return '%s'
|
||||
|
||||
def date_extract_sql(self, lookup_type, field_name):
|
||||
"""
|
||||
Given a lookup_type of 'year', 'month', or 'day', return the SQL that
|
||||
extracts a value from the given date field field_name.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a date_extract_sql() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_extract_sql() method')
|
||||
|
||||
def date_trunc_sql(self, lookup_type, field_name, tzname=None):
|
||||
"""
|
||||
@@ -112,28 +108,22 @@ class BaseDatabaseOperations:
|
||||
If `tzname` is provided, the given value is truncated in a specific
|
||||
timezone.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a date_trunc_sql() "
|
||||
"method."
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_trunc_sql() method.')
|
||||
|
||||
def datetime_cast_date_sql(self, field_name, tzname):
|
||||
"""
|
||||
Return the SQL to cast a datetime value to date value.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a "
|
||||
"datetime_cast_date_sql() method."
|
||||
'subclasses of BaseDatabaseOperations may require a '
|
||||
'datetime_cast_date_sql() method.'
|
||||
)
|
||||
|
||||
def datetime_cast_time_sql(self, field_name, tzname):
|
||||
"""
|
||||
Return the SQL to cast a datetime value to time value.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a "
|
||||
"datetime_cast_time_sql() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_cast_time_sql() method')
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
"""
|
||||
@@ -141,10 +131,7 @@ class BaseDatabaseOperations:
|
||||
'second', return the SQL that extracts a value from the given
|
||||
datetime field field_name.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a datetime_extract_sql() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method')
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
"""
|
||||
@@ -152,10 +139,7 @@ class BaseDatabaseOperations:
|
||||
'second', return the SQL that truncates the given datetime field
|
||||
field_name to a datetime object with only the given specificity.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a datetime_trunc_sql() "
|
||||
"method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunc_sql() method')
|
||||
|
||||
def time_trunc_sql(self, lookup_type, field_name, tzname=None):
|
||||
"""
|
||||
@@ -166,9 +150,7 @@ class BaseDatabaseOperations:
|
||||
If `tzname` is provided, the given value is truncated in a specific
|
||||
timezone.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a time_trunc_sql() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a time_trunc_sql() method')
|
||||
|
||||
def time_extract_sql(self, lookup_type, field_name):
|
||||
"""
|
||||
@@ -182,7 +164,7 @@ class BaseDatabaseOperations:
|
||||
Return the SQL to make a constraint "initially deferred" during a
|
||||
CREATE TABLE statement.
|
||||
"""
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def distinct_sql(self, fields, params):
|
||||
"""
|
||||
@@ -191,11 +173,9 @@ class BaseDatabaseOperations:
|
||||
duplicates.
|
||||
"""
|
||||
if fields:
|
||||
raise NotSupportedError(
|
||||
"DISTINCT ON fields is not supported by this database backend"
|
||||
)
|
||||
raise NotSupportedError('DISTINCT ON fields is not supported by this database backend')
|
||||
else:
|
||||
return ["DISTINCT"], []
|
||||
return ['DISTINCT'], []
|
||||
|
||||
def fetch_returned_insert_columns(self, cursor, returning_params):
|
||||
"""
|
||||
@@ -211,7 +191,7 @@ class BaseDatabaseOperations:
|
||||
it in a WHERE statement. The resulting string should contain a '%s'
|
||||
placeholder for the column being searched against.
|
||||
"""
|
||||
return "%s"
|
||||
return '%s'
|
||||
|
||||
def force_no_ordering(self):
|
||||
"""
|
||||
@@ -224,11 +204,11 @@ class BaseDatabaseOperations:
|
||||
"""
|
||||
Return the FOR UPDATE SQL clause to lock rows for an update operation.
|
||||
"""
|
||||
return "FOR%s UPDATE%s%s%s" % (
|
||||
" NO KEY" if no_key else "",
|
||||
" OF %s" % ", ".join(of) if of else "",
|
||||
" NOWAIT" if nowait else "",
|
||||
" SKIP LOCKED" if skip_locked else "",
|
||||
return 'FOR%s UPDATE%s%s%s' % (
|
||||
' NO KEY' if no_key else '',
|
||||
' OF %s' % ', '.join(of) if of else '',
|
||||
' NOWAIT' if nowait else '',
|
||||
' SKIP LOCKED' if skip_locked else '',
|
||||
)
|
||||
|
||||
def _get_limit_offset_params(self, low_mark, high_mark):
|
||||
@@ -242,14 +222,10 @@ class BaseDatabaseOperations:
|
||||
def limit_offset_sql(self, low_mark, high_mark):
|
||||
"""Return LIMIT/OFFSET SQL clause."""
|
||||
limit, offset = self._get_limit_offset_params(low_mark, high_mark)
|
||||
return " ".join(
|
||||
sql
|
||||
for sql in (
|
||||
("LIMIT %d" % limit) if limit else None,
|
||||
("OFFSET %d" % offset) if offset else None,
|
||||
)
|
||||
if sql
|
||||
)
|
||||
return ' '.join(sql for sql in (
|
||||
('LIMIT %d' % limit) if limit else None,
|
||||
('OFFSET %d' % offset) if offset else None,
|
||||
) if sql)
|
||||
|
||||
def last_executed_query(self, cursor, sql, params):
|
||||
"""
|
||||
@@ -263,8 +239,7 @@ class BaseDatabaseOperations:
|
||||
"""
|
||||
# Convert params to contain string values.
|
||||
def to_string(s):
|
||||
return force_str(s, strings_only=True, errors="replace")
|
||||
|
||||
return force_str(s, strings_only=True, errors='replace')
|
||||
if isinstance(params, (list, tuple)):
|
||||
u_params = tuple(to_string(val) for val in params)
|
||||
elif params is None:
|
||||
@@ -310,16 +285,14 @@ class BaseDatabaseOperations:
|
||||
Return the value to use for the LIMIT when we are wanting "LIMIT
|
||||
infinity". Return None if the limit clause can be omitted in this case.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a no_limit_value() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a no_limit_value() method')
|
||||
|
||||
def pk_default_value(self):
|
||||
"""
|
||||
Return the value to use during an INSERT statement to specify that
|
||||
the field should use its default value.
|
||||
"""
|
||||
return "DEFAULT"
|
||||
return 'DEFAULT'
|
||||
|
||||
def prepare_sql_script(self, sql):
|
||||
"""
|
||||
@@ -332,8 +305,7 @@ class BaseDatabaseOperations:
|
||||
"""
|
||||
return [
|
||||
sqlparse.format(statement, strip_comments=True)
|
||||
for statement in sqlparse.split(sql)
|
||||
if statement
|
||||
for statement in sqlparse.split(sql) if statement
|
||||
]
|
||||
|
||||
def process_clob(self, value):
|
||||
@@ -366,9 +338,7 @@ class BaseDatabaseOperations:
|
||||
Return a quoted version of the given table, index, or column name. Do
|
||||
not quote the given name if it's already been quoted.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a quote_name() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method')
|
||||
|
||||
def regex_lookup(self, lookup_type):
|
||||
"""
|
||||
@@ -379,9 +349,7 @@ class BaseDatabaseOperations:
|
||||
If the feature is not supported (or part of it is not supported), raise
|
||||
NotImplementedError.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations may require a regex_lookup() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a regex_lookup() method')
|
||||
|
||||
def savepoint_create_sql(self, sid):
|
||||
"""
|
||||
@@ -409,7 +377,7 @@ class BaseDatabaseOperations:
|
||||
|
||||
Return '' if the backend doesn't support time zones.
|
||||
"""
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def sql_flush(self, style, tables, *, reset_sequences=False, allow_cascade=False):
|
||||
"""
|
||||
@@ -427,9 +395,7 @@ class BaseDatabaseOperations:
|
||||
to tables with foreign keys pointing the tables being truncated.
|
||||
PostgreSQL requires a cascade even if these tables are empty.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseDatabaseOperations must provide an sql_flush() method"
|
||||
)
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations must provide an sql_flush() method')
|
||||
|
||||
def execute_sql_flush(self, sql_list):
|
||||
"""Execute a list of SQL statements to flush the database."""
|
||||
@@ -480,7 +446,7 @@ class BaseDatabaseOperations:
|
||||
If `inline` is True, append the SQL to a row; otherwise append it to
|
||||
the entire CREATE TABLE or CREATE INDEX statement.
|
||||
"""
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def prep_for_like_query(self, x):
|
||||
"""Prepare a value for use in a LIKE query."""
|
||||
@@ -506,7 +472,7 @@ class BaseDatabaseOperations:
|
||||
cases where the target type isn't known, such as .raw() SQL queries.
|
||||
As a consequence it may not work perfectly in all circumstances.
|
||||
"""
|
||||
if isinstance(value, datetime.datetime): # must be before date
|
||||
if isinstance(value, datetime.datetime): # must be before date
|
||||
return self.adapt_datetimefield_value(value)
|
||||
elif isinstance(value, datetime.date):
|
||||
return self.adapt_datefield_value(value)
|
||||
@@ -560,44 +526,30 @@ class BaseDatabaseOperations:
|
||||
"""
|
||||
return value or None
|
||||
|
||||
def year_lookup_bounds_for_date_field(self, value, iso_year=False):
|
||||
def year_lookup_bounds_for_date_field(self, value):
|
||||
"""
|
||||
Return a two-elements list with the lower and upper bound to be used
|
||||
with a BETWEEN operator to query a DateField value using a year
|
||||
lookup.
|
||||
|
||||
`value` is an int, containing the looked-up year.
|
||||
If `iso_year` is True, return bounds for ISO-8601 week-numbering years.
|
||||
"""
|
||||
if iso_year:
|
||||
first = datetime.date.fromisocalendar(value, 1, 1)
|
||||
second = datetime.date.fromisocalendar(
|
||||
value + 1, 1, 1
|
||||
) - datetime.timedelta(days=1)
|
||||
else:
|
||||
first = datetime.date(value, 1, 1)
|
||||
second = datetime.date(value, 12, 31)
|
||||
first = datetime.date(value, 1, 1)
|
||||
second = datetime.date(value, 12, 31)
|
||||
first = self.adapt_datefield_value(first)
|
||||
second = self.adapt_datefield_value(second)
|
||||
return [first, second]
|
||||
|
||||
def year_lookup_bounds_for_datetime_field(self, value, iso_year=False):
|
||||
def year_lookup_bounds_for_datetime_field(self, value):
|
||||
"""
|
||||
Return a two-elements list with the lower and upper bound to be used
|
||||
with a BETWEEN operator to query a DateTimeField value using a year
|
||||
lookup.
|
||||
|
||||
`value` is an int, containing the looked-up year.
|
||||
If `iso_year` is True, return bounds for ISO-8601 week-numbering years.
|
||||
"""
|
||||
if iso_year:
|
||||
first = datetime.datetime.fromisocalendar(value, 1, 1)
|
||||
second = datetime.datetime.fromisocalendar(
|
||||
value + 1, 1, 1
|
||||
) - datetime.timedelta(microseconds=1)
|
||||
else:
|
||||
first = datetime.datetime(value, 1, 1)
|
||||
second = datetime.datetime(value, 12, 31, 23, 59, 59, 999999)
|
||||
first = datetime.datetime(value, 1, 1)
|
||||
second = datetime.datetime(value, 12, 31, 23, 59, 59, 999999)
|
||||
if settings.USE_TZ:
|
||||
tz = timezone.get_current_timezone()
|
||||
first = timezone.make_aware(first, tz)
|
||||
@@ -644,7 +596,7 @@ class BaseDatabaseOperations:
|
||||
can vary between backends (e.g., Oracle with %% and &) and between
|
||||
subexpression types (e.g., date expressions).
|
||||
"""
|
||||
conn = " %s " % connector
|
||||
conn = ' %s ' % connector
|
||||
return conn.join(sub_expressions)
|
||||
|
||||
def combine_duration_expression(self, connector, sub_expressions):
|
||||
@@ -655,7 +607,7 @@ class BaseDatabaseOperations:
|
||||
Some backends require special syntax to insert binary content (MySQL
|
||||
for example uses '_binary %s').
|
||||
"""
|
||||
return "%s"
|
||||
return '%s'
|
||||
|
||||
def modify_insert_params(self, placeholder, params):
|
||||
"""
|
||||
@@ -676,76 +628,66 @@ class BaseDatabaseOperations:
|
||||
if self.connection.features.supports_temporal_subtraction:
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
return "(%s - %s)" % (lhs_sql, rhs_sql), (*lhs_params, *rhs_params)
|
||||
raise NotSupportedError(
|
||||
"This backend does not support %s subtraction." % internal_type
|
||||
)
|
||||
return '(%s - %s)' % (lhs_sql, rhs_sql), (*lhs_params, *rhs_params)
|
||||
raise NotSupportedError("This backend does not support %s subtraction." % internal_type)
|
||||
|
||||
def window_frame_start(self, start):
|
||||
if isinstance(start, int):
|
||||
if start < 0:
|
||||
return "%d %s" % (abs(start), self.PRECEDING)
|
||||
return '%d %s' % (abs(start), self.PRECEDING)
|
||||
elif start == 0:
|
||||
return self.CURRENT_ROW
|
||||
elif start is None:
|
||||
return self.UNBOUNDED_PRECEDING
|
||||
raise ValueError(
|
||||
"start argument must be a negative integer, zero, or None, but got '%s'."
|
||||
% start
|
||||
)
|
||||
raise ValueError("start argument must be a negative integer, zero, or None, but got '%s'." % start)
|
||||
|
||||
def window_frame_end(self, end):
|
||||
if isinstance(end, int):
|
||||
if end == 0:
|
||||
return self.CURRENT_ROW
|
||||
elif end > 0:
|
||||
return "%d %s" % (end, self.FOLLOWING)
|
||||
return '%d %s' % (end, self.FOLLOWING)
|
||||
elif end is None:
|
||||
return self.UNBOUNDED_FOLLOWING
|
||||
raise ValueError(
|
||||
"end argument must be a positive integer, zero, or None, but got '%s'."
|
||||
% end
|
||||
)
|
||||
raise ValueError("end argument must be a positive integer, zero, or None, but got '%s'." % end)
|
||||
|
||||
def window_frame_rows_start_end(self, start=None, end=None):
|
||||
"""
|
||||
Return SQL for start and end points in an OVER clause window frame.
|
||||
"""
|
||||
if not self.connection.features.supports_over_clause:
|
||||
raise NotSupportedError("This backend does not support window expressions.")
|
||||
raise NotSupportedError('This backend does not support window expressions.')
|
||||
return self.window_frame_start(start), self.window_frame_end(end)
|
||||
|
||||
def window_frame_range_start_end(self, start=None, end=None):
|
||||
start_, end_ = self.window_frame_rows_start_end(start, end)
|
||||
features = self.connection.features
|
||||
if features.only_supports_unbounded_with_preceding_and_following and (
|
||||
(start and start < 0) or (end and end > 0)
|
||||
if (
|
||||
self.connection.features.only_supports_unbounded_with_preceding_and_following and
|
||||
((start and start < 0) or (end and end > 0))
|
||||
):
|
||||
raise NotSupportedError(
|
||||
"%s only supports UNBOUNDED together with PRECEDING and "
|
||||
"FOLLOWING." % self.connection.display_name
|
||||
'%s only supports UNBOUNDED together with PRECEDING and '
|
||||
'FOLLOWING.' % self.connection.display_name
|
||||
)
|
||||
return start_, end_
|
||||
|
||||
def explain_query_prefix(self, format=None, **options):
|
||||
if not self.connection.features.supports_explaining_query_execution:
|
||||
raise NotSupportedError(
|
||||
"This backend does not support explaining query execution."
|
||||
)
|
||||
raise NotSupportedError('This backend does not support explaining query execution.')
|
||||
if format:
|
||||
supported_formats = self.connection.features.supported_explain_formats
|
||||
normalized_format = format.upper()
|
||||
if normalized_format not in supported_formats:
|
||||
msg = "%s is not a recognized format." % normalized_format
|
||||
msg = '%s is not a recognized format.' % normalized_format
|
||||
if supported_formats:
|
||||
msg += " Allowed formats: %s" % ", ".join(sorted(supported_formats))
|
||||
msg += ' Allowed formats: %s' % ', '.join(sorted(supported_formats))
|
||||
raise ValueError(msg)
|
||||
if options:
|
||||
raise ValueError("Unknown options: %s" % ", ".join(sorted(options.keys())))
|
||||
raise ValueError('Unknown options: %s' % ', '.join(sorted(options.keys())))
|
||||
return self.explain_prefix
|
||||
|
||||
def insert_statement(self, ignore_conflicts=False):
|
||||
return "INSERT INTO"
|
||||
return 'INSERT INTO'
|
||||
|
||||
def ignore_conflicts_suffix_sql(self, ignore_conflicts=None):
|
||||
return ""
|
||||
return ''
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
class BaseDatabaseValidation:
|
||||
"""Encapsulate backend-specific validation."""
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
@@ -10,12 +9,9 @@ class BaseDatabaseValidation:
|
||||
def check_field(self, field, **kwargs):
|
||||
errors = []
|
||||
# Backends may implement a check_field_type() method.
|
||||
if (
|
||||
hasattr(self, "check_field_type")
|
||||
and
|
||||
# Ignore any related fields.
|
||||
not getattr(field, "remote_field", None)
|
||||
):
|
||||
if (hasattr(self, 'check_field_type') and
|
||||
# Ignore any related fields.
|
||||
not getattr(field, 'remote_field', None)):
|
||||
# Ignore fields with unsupported features.
|
||||
db_supports_all_required_features = all(
|
||||
getattr(self.connection.features, feature, False)
|
||||
|
||||
Reference in New Issue
Block a user