summaryrefslogtreecommitdiff
path: root/django/db/backends/mysql/operations.py
diff options
context:
space:
mode:
authordjango-bot <ops@djangoproject.com>2022-02-03 20:24:19 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-07 20:37:05 +0100
commit9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch)
treef0506b668a013d0063e5fba3dbf4863b466713ba /django/db/backends/mysql/operations.py
parentf68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff)
downloaddjango-9c19aff7c7561e3a82978a272ecdaad40dda5c00.tar.gz
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'django/db/backends/mysql/operations.py')
-rw-r--r--django/db/backends/mysql/operations.py281
1 files changed, 163 insertions, 118 deletions
diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py
index 7f1994e657..5bcc03a67b 100644
--- a/django/db/backends/mysql/operations.py
+++ b/django/db/backends/mysql/operations.py
@@ -15,42 +15,42 @@ class DatabaseOperations(BaseDatabaseOperations):
# MySQL stores positive fields as UNSIGNED ints.
integer_field_ranges = {
**BaseDatabaseOperations.integer_field_ranges,
- 'PositiveSmallIntegerField': (0, 65535),
- 'PositiveIntegerField': (0, 4294967295),
- 'PositiveBigIntegerField': (0, 18446744073709551615),
+ "PositiveSmallIntegerField": (0, 65535),
+ "PositiveIntegerField": (0, 4294967295),
+ "PositiveBigIntegerField": (0, 18446744073709551615),
}
cast_data_types = {
- 'AutoField': 'signed integer',
- 'BigAutoField': 'signed integer',
- 'SmallAutoField': 'signed integer',
- 'CharField': 'char(%(max_length)s)',
- 'DecimalField': 'decimal(%(max_digits)s, %(decimal_places)s)',
- 'TextField': 'char',
- 'IntegerField': 'signed integer',
- 'BigIntegerField': 'signed integer',
- 'SmallIntegerField': 'signed integer',
- 'PositiveBigIntegerField': 'unsigned integer',
- 'PositiveIntegerField': 'unsigned integer',
- 'PositiveSmallIntegerField': 'unsigned integer',
- 'DurationField': 'signed integer',
+ "AutoField": "signed integer",
+ "BigAutoField": "signed integer",
+ "SmallAutoField": "signed integer",
+ "CharField": "char(%(max_length)s)",
+ "DecimalField": "decimal(%(max_digits)s, %(decimal_places)s)",
+ "TextField": "char",
+ "IntegerField": "signed integer",
+ "BigIntegerField": "signed integer",
+ "SmallIntegerField": "signed integer",
+ "PositiveBigIntegerField": "unsigned integer",
+ "PositiveIntegerField": "unsigned integer",
+ "PositiveSmallIntegerField": "unsigned integer",
+ "DurationField": "signed integer",
}
- cast_char_field_without_max_length = 'char'
- explain_prefix = 'EXPLAIN'
+ cast_char_field_without_max_length = "char"
+ explain_prefix = "EXPLAIN"
def date_extract_sql(self, lookup_type, field_name):
# https://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
- if lookup_type == 'week_day':
+ if lookup_type == "week_day":
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
return "DAYOFWEEK(%s)" % field_name
- elif lookup_type == 'iso_week_day':
+ elif lookup_type == "iso_week_day":
# WEEKDAY() returns an integer, 0-6, Monday=0.
return "WEEKDAY(%s) + 1" % field_name
- elif lookup_type == 'week':
+ elif lookup_type == "week":
# Override the value of default_week_format for consistency with
# other database backends.
# Mode 3: Monday, 1-53, with 4 or more days this year.
return "WEEK(%s, 3)" % field_name
- elif lookup_type == 'iso_year':
+ elif lookup_type == "iso_year":
# Get the year part from the YEARWEEK function, which returns a
# number as year * 100 + week.
return "TRUNCATE(YEARWEEK(%s, 3), -2) / 100" % field_name
@@ -61,26 +61,25 @@ class DatabaseOperations(BaseDatabaseOperations):
def date_trunc_sql(self, lookup_type, field_name, tzname=None):
field_name = self._convert_field_to_tz(field_name, tzname)
fields = {
- 'year': '%%Y-01-01',
- 'month': '%%Y-%%m-01',
+ "year": "%%Y-01-01",
+ "month": "%%Y-%%m-01",
} # Use double percents to escape.
if lookup_type in fields:
format_str = fields[lookup_type]
return "CAST(DATE_FORMAT(%s, '%s') AS DATE)" % (field_name, format_str)
- elif lookup_type == 'quarter':
- return "MAKEDATE(YEAR(%s), 1) + INTERVAL QUARTER(%s) QUARTER - INTERVAL 1 QUARTER" % (
- field_name, field_name
- )
- elif lookup_type == 'week':
- return "DATE_SUB(%s, INTERVAL WEEKDAY(%s) DAY)" % (
- field_name, field_name
+ elif lookup_type == "quarter":
+ return (
+ "MAKEDATE(YEAR(%s), 1) + INTERVAL QUARTER(%s) QUARTER - INTERVAL 1 QUARTER"
+ % (field_name, field_name)
)
+ elif lookup_type == "week":
+ return "DATE_SUB(%s, INTERVAL WEEKDAY(%s) DAY)" % (field_name, field_name)
else:
return "DATE(%s)" % (field_name)
def _prepare_tzname_delta(self, tzname):
tzname, sign, offset = split_tzname_delta(tzname)
- return f'{sign}{offset}' if offset else tzname
+ return f"{sign}{offset}" if offset else tzname
def _convert_field_to_tz(self, field_name, tzname):
if tzname and settings.USE_TZ and self.connection.timezone_name != tzname:
@@ -105,16 +104,23 @@ class DatabaseOperations(BaseDatabaseOperations):
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
field_name = self._convert_field_to_tz(field_name, tzname)
- fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
- format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
- format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
- if lookup_type == 'quarter':
+ fields = ["year", "month", "day", "hour", "minute", "second"]
+ format = (
+ "%%Y-",
+ "%%m",
+ "-%%d",
+ " %%H:",
+ "%%i",
+ ":%%s",
+ ) # Use double percents to escape.
+ format_def = ("0000-", "01", "-01", " 00:", "00", ":00")
+ if lookup_type == "quarter":
return (
"CAST(DATE_FORMAT(MAKEDATE(YEAR({field_name}), 1) + "
- "INTERVAL QUARTER({field_name}) QUARTER - " +
- "INTERVAL 1 QUARTER, '%%Y-%%m-01 00:00:00') AS DATETIME)"
+ "INTERVAL QUARTER({field_name}) QUARTER - "
+ + "INTERVAL 1 QUARTER, '%%Y-%%m-01 00:00:00') AS DATETIME)"
).format(field_name=field_name)
- if lookup_type == 'week':
+ if lookup_type == "week":
return (
"CAST(DATE_FORMAT(DATE_SUB({field_name}, "
"INTERVAL WEEKDAY({field_name}) DAY), "
@@ -125,16 +131,16 @@ class DatabaseOperations(BaseDatabaseOperations):
except ValueError:
sql = field_name
else:
- format_str = ''.join(format[:i] + format_def[i:])
+ format_str = "".join(format[:i] + format_def[i:])
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
return sql
def time_trunc_sql(self, lookup_type, field_name, tzname=None):
field_name = self._convert_field_to_tz(field_name, tzname)
fields = {
- 'hour': '%%H:00:00',
- 'minute': '%%H:%%i:00',
- 'second': '%%H:%%i:%%s',
+ "hour": "%%H:00:00",
+ "minute": "%%H:%%i:00",
+ "second": "%%H:%%i:%%s",
} # Use double percents to escape.
if lookup_type in fields:
format_str = fields[lookup_type]
@@ -150,7 +156,7 @@ class DatabaseOperations(BaseDatabaseOperations):
return cursor.fetchall()
def format_for_duration_arithmetic(self, sql):
- return 'INTERVAL %s MICROSECOND' % sql
+ return "INTERVAL %s MICROSECOND" % sql
def force_no_ordering(self):
"""
@@ -168,7 +174,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# attribute where the exact query sent to the database is saved.
# See MySQLdb/cursors.py in the source distribution.
# MySQLdb returns string, PyMySQL bytes.
- return force_str(getattr(cursor, '_executed', None), errors='replace')
+ return force_str(getattr(cursor, "_executed", None), errors="replace")
def no_limit_value(self):
# 2**64 - 1, as recommended by the MySQL documentation
@@ -183,50 +189,58 @@ class DatabaseOperations(BaseDatabaseOperations):
# MySQL and MariaDB < 10.5.0 don't support an INSERT...RETURNING
# statement.
if not fields:
- return '', ()
+ return "", ()
columns = [
- '%s.%s' % (
+ "%s.%s"
+ % (
self.quote_name(field.model._meta.db_table),
self.quote_name(field.column),
- ) for field in fields
+ )
+ for field in fields
]
- return 'RETURNING %s' % ', '.join(columns), ()
+ return "RETURNING %s" % ", ".join(columns), ()
def sql_flush(self, style, tables, *, reset_sequences=False, allow_cascade=False):
if not tables:
return []
- sql = ['SET FOREIGN_KEY_CHECKS = 0;']
+ sql = ["SET FOREIGN_KEY_CHECKS = 0;"]
if reset_sequences:
# It's faster to TRUNCATE tables that require a sequence reset
# since ALTER TABLE AUTO_INCREMENT is slower than TRUNCATE.
sql.extend(
- '%s %s;' % (
- style.SQL_KEYWORD('TRUNCATE'),
+ "%s %s;"
+ % (
+ style.SQL_KEYWORD("TRUNCATE"),
style.SQL_FIELD(self.quote_name(table_name)),
- ) for table_name in tables
+ )
+ for table_name in tables
)
else:
# Otherwise issue a simple DELETE since it's faster than TRUNCATE
# and preserves sequences.
sql.extend(
- '%s %s %s;' % (
- style.SQL_KEYWORD('DELETE'),
- style.SQL_KEYWORD('FROM'),
+ "%s %s %s;"
+ % (
+ style.SQL_KEYWORD("DELETE"),
+ style.SQL_KEYWORD("FROM"),
style.SQL_FIELD(self.quote_name(table_name)),
- ) for table_name in tables
+ )
+ for table_name in tables
)
- sql.append('SET FOREIGN_KEY_CHECKS = 1;')
+ sql.append("SET FOREIGN_KEY_CHECKS = 1;")
return sql
def sequence_reset_by_name_sql(self, style, sequences):
return [
- '%s %s %s %s = 1;' % (
- style.SQL_KEYWORD('ALTER'),
- style.SQL_KEYWORD('TABLE'),
- style.SQL_FIELD(self.quote_name(sequence_info['table'])),
- style.SQL_FIELD('AUTO_INCREMENT'),
- ) for sequence_info in sequences
+ "%s %s %s %s = 1;"
+ % (
+ style.SQL_KEYWORD("ALTER"),
+ style.SQL_KEYWORD("TABLE"),
+ style.SQL_FIELD(self.quote_name(sequence_info["table"])),
+ style.SQL_FIELD("AUTO_INCREMENT"),
+ )
+ for sequence_info in sequences
]
def validate_autopk_value(self, value):
@@ -234,7 +248,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# NO_AUTO_VALUE_ON_ZERO SQL mode.
if value == 0 and not self.connection.features.allows_auto_pk_0:
raise ValueError(
- 'The database backend does not accept 0 as a value for AutoField.'
+ "The database backend does not accept 0 as a value for AutoField."
)
return value
@@ -243,7 +257,7 @@ class DatabaseOperations(BaseDatabaseOperations):
return None
# Expression values are adapted by the database.
- if hasattr(value, 'resolve_expression'):
+ if hasattr(value, "resolve_expression"):
return value
# MySQL doesn't support tz-aware datetimes
@@ -251,7 +265,9 @@ class DatabaseOperations(BaseDatabaseOperations):
if settings.USE_TZ:
value = timezone.make_naive(value, self.connection.timezone)
else:
- raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
+ raise ValueError(
+ "MySQL backend does not support timezone-aware datetimes when USE_TZ is False."
+ )
return str(value)
def adapt_timefield_value(self, value):
@@ -259,20 +275,20 @@ class DatabaseOperations(BaseDatabaseOperations):
return None
# Expression values are adapted by the database.
- if hasattr(value, 'resolve_expression'):
+ if hasattr(value, "resolve_expression"):
return value
# MySQL doesn't support tz-aware times
if timezone.is_aware(value):
raise ValueError("MySQL backend does not support timezone-aware times.")
- return value.isoformat(timespec='microseconds')
+ return value.isoformat(timespec="microseconds")
def max_name_length(self):
return 64
def pk_default_value(self):
- return 'NULL'
+ return "NULL"
def bulk_insert_sql(self, fields, placeholder_rows):
placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
@@ -280,27 +296,27 @@ class DatabaseOperations(BaseDatabaseOperations):
return "VALUES " + values_sql
def combine_expression(self, connector, sub_expressions):
- if connector == '^':
- return 'POW(%s)' % ','.join(sub_expressions)
+ if connector == "^":
+ return "POW(%s)" % ",".join(sub_expressions)
# Convert the result to a signed integer since MySQL's binary operators
# return an unsigned integer.
- elif connector in ('&', '|', '<<', '#'):
- connector = '^' if connector == '#' else connector
- return 'CONVERT(%s, SIGNED)' % connector.join(sub_expressions)
- elif connector == '>>':
+ elif connector in ("&", "|", "<<", "#"):
+ connector = "^" if connector == "#" else connector
+ return "CONVERT(%s, SIGNED)" % connector.join(sub_expressions)
+ elif connector == ">>":
lhs, rhs = sub_expressions
- return 'FLOOR(%(lhs)s / POW(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
+ return "FLOOR(%(lhs)s / POW(2, %(rhs)s))" % {"lhs": lhs, "rhs": rhs}
return super().combine_expression(connector, sub_expressions)
def get_db_converters(self, expression):
converters = super().get_db_converters(expression)
internal_type = expression.output_field.get_internal_type()
- if internal_type == 'BooleanField':
+ if internal_type == "BooleanField":
converters.append(self.convert_booleanfield_value)
- elif internal_type == 'DateTimeField':
+ elif internal_type == "DateTimeField":
if settings.USE_TZ:
converters.append(self.convert_datetimefield_value)
- elif internal_type == 'UUIDField':
+ elif internal_type == "UUIDField":
converters.append(self.convert_uuidfield_value)
return converters
@@ -320,66 +336,88 @@ class DatabaseOperations(BaseDatabaseOperations):
return value
def binary_placeholder_sql(self, value):
- return '_binary %s' if value is not None and not hasattr(value, 'as_sql') else '%s'
+ return (
+ "_binary %s" if value is not None and not hasattr(value, "as_sql") else "%s"
+ )
def subtract_temporals(self, internal_type, lhs, rhs):
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
- if internal_type == 'TimeField':
+ if internal_type == "TimeField":
if self.connection.mysql_is_mariadb:
# MariaDB includes the microsecond component in TIME_TO_SEC as
# a decimal. MySQL returns an integer without microseconds.
- return 'CAST((TIME_TO_SEC(%(lhs)s) - TIME_TO_SEC(%(rhs)s)) * 1000000 AS SIGNED)' % {
- 'lhs': lhs_sql, 'rhs': rhs_sql
- }, (*lhs_params, *rhs_params)
+ return "CAST((TIME_TO_SEC(%(lhs)s) - TIME_TO_SEC(%(rhs)s)) * 1000000 AS SIGNED)" % {
+ "lhs": lhs_sql,
+ "rhs": rhs_sql,
+ }, (
+ *lhs_params,
+ *rhs_params,
+ )
return (
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
- ) % {'lhs': lhs_sql, 'rhs': rhs_sql}, tuple(lhs_params) * 2 + tuple(rhs_params) * 2
+ ) % {"lhs": lhs_sql, "rhs": rhs_sql}, tuple(lhs_params) * 2 + tuple(
+ rhs_params
+ ) * 2
params = (*rhs_params, *lhs_params)
return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), params
def explain_query_prefix(self, format=None, **options):
# Alias MySQL's TRADITIONAL to TEXT for consistency with other backends.
- if format and format.upper() == 'TEXT':
- format = 'TRADITIONAL'
- elif not format and 'TREE' in self.connection.features.supported_explain_formats:
+ if format and format.upper() == "TEXT":
+ format = "TRADITIONAL"
+ elif (
+ not format and "TREE" in self.connection.features.supported_explain_formats
+ ):
# Use TREE by default (if supported) as it's more informative.
- format = 'TREE'
- analyze = options.pop('analyze', False)
+ format = "TREE"
+ analyze = options.pop("analyze", False)
prefix = super().explain_query_prefix(format, **options)
if analyze and self.connection.features.supports_explain_analyze:
# MariaDB uses ANALYZE instead of EXPLAIN ANALYZE.
- prefix = 'ANALYZE' if self.connection.mysql_is_mariadb else prefix + ' ANALYZE'
+ prefix = (
+ "ANALYZE" if self.connection.mysql_is_mariadb else prefix + " ANALYZE"
+ )
if format and not (analyze and not self.connection.mysql_is_mariadb):
# Only MariaDB supports the analyze option with formats.
- prefix += ' FORMAT=%s' % format
+ prefix += " FORMAT=%s" % format
return prefix
def regex_lookup(self, lookup_type):
# REGEXP BINARY doesn't work correctly in MySQL 8+ and REGEXP_LIKE
# doesn't exist in MySQL 5.x or in MariaDB.
- if self.connection.mysql_version < (8, 0, 0) or self.connection.mysql_is_mariadb:
- if lookup_type == 'regex':
- return '%s REGEXP BINARY %s'
- return '%s REGEXP %s'
-
- match_option = 'c' if lookup_type == 'regex' else 'i'
+ if (
+ self.connection.mysql_version < (8, 0, 0)
+ or self.connection.mysql_is_mariadb
+ ):
+ if lookup_type == "regex":
+ return "%s REGEXP BINARY %s"
+ return "%s REGEXP %s"
+
+ match_option = "c" if lookup_type == "regex" else "i"
return "REGEXP_LIKE(%%s, %%s, '%s')" % match_option
def insert_statement(self, on_conflict=None):
if on_conflict == OnConflict.IGNORE:
- return 'INSERT IGNORE INTO'
+ return "INSERT IGNORE INTO"
return super().insert_statement(on_conflict=on_conflict)
def lookup_cast(self, lookup_type, internal_type=None):
- lookup = '%s'
- if internal_type == 'JSONField':
+ lookup = "%s"
+ if internal_type == "JSONField":
if self.connection.mysql_is_mariadb or lookup_type in (
- 'iexact', 'contains', 'icontains', 'startswith', 'istartswith',
- 'endswith', 'iendswith', 'regex', 'iregex',
+ "iexact",
+ "contains",
+ "icontains",
+ "startswith",
+ "istartswith",
+ "endswith",
+ "iendswith",
+ "regex",
+ "iregex",
):
- lookup = 'JSON_UNQUOTE(%s)'
+ lookup = "JSON_UNQUOTE(%s)"
return lookup
def conditional_expression_supported_in_where_clause(self, expression):
@@ -388,31 +426,38 @@ class DatabaseOperations(BaseDatabaseOperations):
if isinstance(expression, (Exists, Lookup)):
return True
if isinstance(expression, ExpressionWrapper) and expression.conditional:
- return self.conditional_expression_supported_in_where_clause(expression.expression)
- if getattr(expression, 'conditional', False):
+ return self.conditional_expression_supported_in_where_clause(
+ expression.expression
+ )
+ if getattr(expression, "conditional", False):
return False
return super().conditional_expression_supported_in_where_clause(expression)
def on_conflict_suffix_sql(self, fields, on_conflict, update_fields, unique_fields):
if on_conflict == OnConflict.UPDATE:
- conflict_suffix_sql = 'ON DUPLICATE KEY UPDATE %(fields)s'
- field_sql = '%(field)s = VALUES(%(field)s)'
+ conflict_suffix_sql = "ON DUPLICATE KEY UPDATE %(fields)s"
+ field_sql = "%(field)s = VALUES(%(field)s)"
# The use of VALUES() is deprecated in MySQL 8.0.20+. Instead, use
# aliases for the new row and its columns available in MySQL
# 8.0.19+.
if not self.connection.mysql_is_mariadb:
if self.connection.mysql_version >= (8, 0, 19):
- conflict_suffix_sql = f'AS new {conflict_suffix_sql}'
- field_sql = '%(field)s = new.%(field)s'
+ conflict_suffix_sql = f"AS new {conflict_suffix_sql}"
+ field_sql = "%(field)s = new.%(field)s"
# VALUES() was renamed to VALUE() in MariaDB 10.3.3+.
elif self.connection.mysql_version >= (10, 3, 3):
- field_sql = '%(field)s = VALUE(%(field)s)'
+ field_sql = "%(field)s = VALUE(%(field)s)"
- fields = ', '.join([
- field_sql % {'field': field}
- for field in map(self.quote_name, update_fields)
- ])
- return conflict_suffix_sql % {'fields': fields}
+ fields = ", ".join(
+ [
+ field_sql % {"field": field}
+ for field in map(self.quote_name, update_fields)
+ ]
+ )
+ return conflict_suffix_sql % {"fields": fields}
return super().on_conflict_suffix_sql(
- fields, on_conflict, update_fields, unique_fields,
+ fields,
+ on_conflict,
+ update_fields,
+ unique_fields,
)