Cleaned up the directories
This commit is contained in:
parent
f708506d68
commit
a683fcffea
1340 changed files with 554582 additions and 6840 deletions
|
@ -0,0 +1,567 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
"""Django database Backend using MySQL Connector/Python
|
||||
|
||||
This Django database backend is heavily based on the MySQL backend coming
|
||||
with Django.
|
||||
|
||||
Changes include:
|
||||
* Support for microseconds (MySQL 5.6.3 and later)
|
||||
* Using INFORMATION_SCHEMA where possible
|
||||
* Using new defaults for, for example SQL_AUTO_IS_NULL
|
||||
|
||||
Requires and comes with MySQL Connector/Python v1.1 and later:
|
||||
http://dev.mysql.com/downloads/connector/python/
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import django
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
try:
|
||||
import mysql.connector
|
||||
from mysql.connector.conversion import MySQLConverter, MySQLConverterBase
|
||||
from mysql.connector.catch23 import PY2
|
||||
except ImportError as err:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured(
|
||||
"Error loading mysql.connector module: {0}".format(err))
|
||||
|
||||
try:
|
||||
version = mysql.connector.__version_info__[0:3]
|
||||
except AttributeError:
|
||||
from mysql.connector.version import VERSION
|
||||
version = VERSION[0:3]
|
||||
|
||||
try:
|
||||
from _mysql_connector import datetime_to_mysql, time_to_mysql
|
||||
except ImportError:
|
||||
HAVE_CEXT = False
|
||||
else:
|
||||
HAVE_CEXT = True
|
||||
|
||||
if version < (1, 1):
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured(
|
||||
"MySQL Connector/Python v1.1.0 or newer "
|
||||
"is required; you have %s" % mysql.connector.__version__)
|
||||
|
||||
from django.db import utils
|
||||
if django.VERSION < (1, 7):
|
||||
from django.db.backends import util
|
||||
else:
|
||||
from django.db.backends import utils as backend_utils
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.base import BaseDatabaseWrapper
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseWrapper
|
||||
|
||||
from django.db.backends.signals import connection_created
|
||||
from django.utils import (six, timezone, dateparse)
|
||||
from django.conf import settings
|
||||
|
||||
from mysql.connector.django.client import DatabaseClient
|
||||
from mysql.connector.django.creation import DatabaseCreation
|
||||
from mysql.connector.django.introspection import DatabaseIntrospection
|
||||
from mysql.connector.django.validation import DatabaseValidation
|
||||
from mysql.connector.django.features import DatabaseFeatures
|
||||
from mysql.connector.django.operations import DatabaseOperations
|
||||
if django.VERSION >= (1, 7):
|
||||
from mysql.connector.django.schema import DatabaseSchemaEditor
|
||||
|
||||
|
||||
DatabaseError = mysql.connector.DatabaseError
|
||||
IntegrityError = mysql.connector.IntegrityError
|
||||
NotSupportedError = mysql.connector.NotSupportedError
|
||||
|
||||
|
||||
def adapt_datetime_with_timezone_support(value):
|
||||
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
|
||||
if settings.USE_TZ:
|
||||
if timezone.is_naive(value):
|
||||
warnings.warn("MySQL received a naive datetime (%s)"
|
||||
" while time zone support is active." % value,
|
||||
RuntimeWarning)
|
||||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_aware(value, default_timezone)
|
||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
if HAVE_CEXT:
|
||||
return datetime_to_mysql(value)
|
||||
else:
|
||||
return value.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
|
||||
class DjangoMySQLConverter(MySQLConverter):
|
||||
"""Custom converter for Django for MySQLConnection"""
|
||||
def _TIME_to_python(self, value, dsc=None):
|
||||
"""Return MySQL TIME data type as datetime.time()
|
||||
|
||||
Returns datetime.time()
|
||||
"""
|
||||
return dateparse.parse_time(value.decode('utf-8'))
|
||||
|
||||
def _DATETIME_to_python(self, value, dsc=None):
|
||||
"""Connector/Python always returns naive datetime.datetime
|
||||
|
||||
Connector/Python always returns naive timestamps since MySQL has
|
||||
no time zone support. Since Django needs non-naive, we need to add
|
||||
the UTC time zone.
|
||||
|
||||
Returns datetime.datetime()
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
dt = MySQLConverter._DATETIME_to_python(self, value)
|
||||
if dt is None:
|
||||
return None
|
||||
if settings.USE_TZ and timezone.is_naive(dt):
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt
|
||||
|
||||
def _safetext_to_mysql(self, value):
|
||||
if PY2:
|
||||
return self._unicode_to_mysql(value)
|
||||
else:
|
||||
return self._str_to_mysql(value)
|
||||
|
||||
def _safebytes_to_mysql(self, value):
|
||||
return self._bytes_to_mysql(value)
|
||||
|
||||
|
||||
class DjangoCMySQLConverter(MySQLConverterBase):
|
||||
"""Custom converter for Django for CMySQLConnection"""
|
||||
def _TIME_to_python(self, value, dsc=None):
|
||||
"""Return MySQL TIME data type as datetime.time()
|
||||
|
||||
Returns datetime.time()
|
||||
"""
|
||||
return dateparse.parse_time(str(value))
|
||||
|
||||
def _DATETIME_to_python(self, value, dsc=None):
|
||||
"""Connector/Python always returns naive datetime.datetime
|
||||
|
||||
Connector/Python always returns naive timestamps since MySQL has
|
||||
no time zone support. Since Django needs non-naive, we need to add
|
||||
the UTC time zone.
|
||||
|
||||
Returns datetime.datetime()
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
if settings.USE_TZ and timezone.is_naive(value):
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
|
||||
class CursorWrapper(object):
|
||||
"""Wrapper around MySQL Connector/Python's cursor class.
|
||||
|
||||
The cursor class is defined by the options passed to MySQL
|
||||
Connector/Python. If buffered option is True in those options,
|
||||
MySQLCursorBuffered will be used.
|
||||
"""
|
||||
codes_for_integrityerror = (1048,)
|
||||
|
||||
def __init__(self, cursor):
|
||||
self.cursor = cursor
|
||||
|
||||
def _execute_wrapper(self, method, query, args):
|
||||
"""Wrapper around execute() and executemany()"""
|
||||
try:
|
||||
return method(query, args)
|
||||
except (mysql.connector.ProgrammingError) as err:
|
||||
six.reraise(utils.ProgrammingError,
|
||||
utils.ProgrammingError(err.msg), sys.exc_info()[2])
|
||||
except (mysql.connector.IntegrityError) as err:
|
||||
six.reraise(utils.IntegrityError,
|
||||
utils.IntegrityError(err.msg), sys.exc_info()[2])
|
||||
except mysql.connector.OperationalError as err:
|
||||
# Map some error codes to IntegrityError, since they seem to be
|
||||
# misclassified and Django would prefer the more logical place.
|
||||
if err.args[0] in self.codes_for_integrityerror:
|
||||
six.reraise(utils.IntegrityError,
|
||||
utils.IntegrityError(err.msg), sys.exc_info()[2])
|
||||
else:
|
||||
six.reraise(utils.DatabaseError,
|
||||
utils.DatabaseError(err.msg), sys.exc_info()[2])
|
||||
except mysql.connector.DatabaseError as err:
|
||||
six.reraise(utils.DatabaseError,
|
||||
utils.DatabaseError(err.msg), sys.exc_info()[2])
|
||||
|
||||
def _adapt_execute_args_dict(self, args):
|
||||
if not args:
|
||||
return args
|
||||
new_args = dict(args)
|
||||
for key, value in args.items():
|
||||
if isinstance(value, datetime):
|
||||
new_args[key] = adapt_datetime_with_timezone_support(value)
|
||||
|
||||
return new_args
|
||||
|
||||
def _adapt_execute_args(self, args):
|
||||
if not args:
|
||||
return args
|
||||
new_args = list(args)
|
||||
for i, arg in enumerate(args):
|
||||
if isinstance(arg, datetime):
|
||||
new_args[i] = adapt_datetime_with_timezone_support(arg)
|
||||
|
||||
return tuple(new_args)
|
||||
|
||||
def execute(self, query, args=None):
|
||||
"""Executes the given operation
|
||||
|
||||
This wrapper method around the execute()-method of the cursor is
|
||||
mainly needed to re-raise using different exceptions.
|
||||
"""
|
||||
if isinstance(args, dict):
|
||||
new_args = self._adapt_execute_args_dict(args)
|
||||
else:
|
||||
new_args = self._adapt_execute_args(args)
|
||||
return self._execute_wrapper(self.cursor.execute, query, new_args)
|
||||
|
||||
def executemany(self, query, args):
|
||||
"""Executes the given operation
|
||||
|
||||
This wrapper method around the executemany()-method of the cursor is
|
||||
mainly needed to re-raise using different exceptions.
|
||||
"""
|
||||
return self._execute_wrapper(self.cursor.executemany, query, args)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""Return attribute of wrapped cursor"""
|
||||
return getattr(self.cursor, attr)
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns iterator over wrapped cursor"""
|
||||
return iter(self.cursor)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
vendor = 'mysql'
|
||||
# This dictionary maps Field objects to their associated MySQL column
|
||||
# types, as strings. Column-type strings can contain format strings; they'll
|
||||
# be interpolated against the values of Field.__dict__ before being output.
|
||||
# If a column type is set to None, it won't be included in the output.
|
||||
|
||||
# Moved from DatabaseCreation class in Django v1.8
|
||||
_data_types = {
|
||||
'AutoField': 'integer AUTO_INCREMENT',
|
||||
'BinaryField': 'longblob',
|
||||
'BooleanField': 'bool',
|
||||
'CharField': 'varchar(%(max_length)s)',
|
||||
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
|
||||
'DateField': 'date',
|
||||
'DateTimeField': 'datetime',
|
||||
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'DurationField': 'bigint',
|
||||
'FileField': 'varchar(%(max_length)s)',
|
||||
'FilePathField': 'varchar(%(max_length)s)',
|
||||
'FloatField': 'double precision',
|
||||
'IntegerField': 'integer',
|
||||
'BigIntegerField': 'bigint',
|
||||
'IPAddressField': 'char(15)',
|
||||
'GenericIPAddressField': 'char(39)',
|
||||
'NullBooleanField': 'bool',
|
||||
'OneToOneField': 'integer',
|
||||
'PositiveIntegerField': 'integer UNSIGNED',
|
||||
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||
'SlugField': 'varchar(%(max_length)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'longtext',
|
||||
'TimeField': 'time',
|
||||
'UUIDField': 'char(32)',
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def data_types(self):
|
||||
if self.features.supports_microsecond_precision:
|
||||
return dict(self._data_types, DateTimeField='datetime(6)',
|
||||
TimeField='time(6)')
|
||||
else:
|
||||
return self._data_types
|
||||
|
||||
operators = {
|
||||
'exact': '= %s',
|
||||
'iexact': 'LIKE %s',
|
||||
'contains': 'LIKE BINARY %s',
|
||||
'icontains': 'LIKE %s',
|
||||
'regex': 'REGEXP BINARY %s',
|
||||
'iregex': 'REGEXP %s',
|
||||
'gt': '> %s',
|
||||
'gte': '>= %s',
|
||||
'lt': '< %s',
|
||||
'lte': '<= %s',
|
||||
'startswith': 'LIKE BINARY %s',
|
||||
'endswith': 'LIKE BINARY %s',
|
||||
'istartswith': 'LIKE %s',
|
||||
'iendswith': 'LIKE %s',
|
||||
}
|
||||
|
||||
# The patterns below are used to generate SQL pattern lookup clauses when
|
||||
# the right-hand side of the lookup isn't a raw string (it might be an
|
||||
# expression or the result of a bilateral transformation).
|
||||
# In those cases, special characters for LIKE operators (e.g. \, *, _)
|
||||
# should be escaped on database side.
|
||||
#
|
||||
# Note: we use str.format() here for readability as '%' is used as a
|
||||
# wildcard for the LIKE operator.
|
||||
pattern_esc = (r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'),"
|
||||
r" '%%', '\%%'), '_', '\_')")
|
||||
pattern_ops = {
|
||||
'contains': "LIKE BINARY CONCAT('%%', {}, '%%')",
|
||||
'icontains': "LIKE CONCAT('%%', {}, '%%')",
|
||||
'startswith': "LIKE BINARY CONCAT({}, '%%')",
|
||||
'istartswith': "LIKE CONCAT({}, '%%')",
|
||||
'endswith': "LIKE BINARY CONCAT('%%', {})",
|
||||
'iendswith': "LIKE CONCAT('%%', {})",
|
||||
}
|
||||
|
||||
SchemaEditorClass = DatabaseSchemaEditor
|
||||
Database = mysql.connector
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
|
||||
try:
|
||||
self._use_pure = self.settings_dict['OPTIONS']['use_pure']
|
||||
except KeyError:
|
||||
self._use_pure = True
|
||||
|
||||
if not self.use_pure:
|
||||
self.converter = DjangoCMySQLConverter()
|
||||
else:
|
||||
self.converter = DjangoMySQLConverter()
|
||||
self.ops = DatabaseOperations(self)
|
||||
self.features = DatabaseFeatures(self)
|
||||
self.client = DatabaseClient(self)
|
||||
self.creation = DatabaseCreation(self)
|
||||
self.introspection = DatabaseIntrospection(self)
|
||||
self.validation = DatabaseValidation(self)
|
||||
|
||||
def _valid_connection(self):
|
||||
if self.connection:
|
||||
return self.connection.is_connected()
|
||||
return False
|
||||
|
||||
def get_connection_params(self):
|
||||
# Django 1.6
|
||||
kwargs = {
|
||||
'charset': 'utf8',
|
||||
'use_unicode': True,
|
||||
'buffered': False,
|
||||
'consume_results': True,
|
||||
}
|
||||
|
||||
settings_dict = self.settings_dict
|
||||
|
||||
if settings_dict['USER']:
|
||||
kwargs['user'] = settings_dict['USER']
|
||||
if settings_dict['NAME']:
|
||||
kwargs['database'] = settings_dict['NAME']
|
||||
if settings_dict['PASSWORD']:
|
||||
kwargs['passwd'] = settings_dict['PASSWORD']
|
||||
if settings_dict['HOST'].startswith('/'):
|
||||
kwargs['unix_socket'] = settings_dict['HOST']
|
||||
elif settings_dict['HOST']:
|
||||
kwargs['host'] = settings_dict['HOST']
|
||||
if settings_dict['PORT']:
|
||||
kwargs['port'] = int(settings_dict['PORT'])
|
||||
|
||||
# Raise exceptions for database warnings if DEBUG is on
|
||||
kwargs['raise_on_warnings'] = settings.DEBUG
|
||||
|
||||
kwargs['client_flags'] = [
|
||||
# Need potentially affected rows on UPDATE
|
||||
mysql.connector.constants.ClientFlag.FOUND_ROWS,
|
||||
]
|
||||
try:
|
||||
kwargs.update(settings_dict['OPTIONS'])
|
||||
except KeyError:
|
||||
# OPTIONS missing is OK
|
||||
pass
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_new_connection(self, conn_params):
|
||||
# Django 1.6
|
||||
if not self.use_pure:
|
||||
conn_params['converter_class'] = DjangoCMySQLConverter
|
||||
else:
|
||||
conn_params['converter_class'] = DjangoMySQLConverter
|
||||
cnx = mysql.connector.connect(**conn_params)
|
||||
|
||||
return cnx
|
||||
|
||||
def init_connection_state(self):
|
||||
# Django 1.6
|
||||
if self.mysql_version < (5, 5, 3):
|
||||
# See sysvar_sql_auto_is_null in MySQL Reference manual
|
||||
self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0")
|
||||
|
||||
if 'AUTOCOMMIT' in self.settings_dict:
|
||||
try:
|
||||
# Django 1.6
|
||||
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
|
||||
except AttributeError:
|
||||
self._set_autocommit(self.settings_dict['AUTOCOMMIT'])
|
||||
|
||||
def create_cursor(self):
|
||||
# Django 1.6
|
||||
cursor = self.connection.cursor()
|
||||
return CursorWrapper(cursor)
|
||||
|
||||
def _connect(self):
|
||||
"""Setup the connection with MySQL"""
|
||||
self.connection = self.get_new_connection(self.get_connection_params())
|
||||
connection_created.send(sender=self.__class__, connection=self)
|
||||
self.init_connection_state()
|
||||
|
||||
def _cursor(self):
|
||||
"""Return a CursorWrapper object
|
||||
|
||||
Returns a CursorWrapper
|
||||
"""
|
||||
try:
|
||||
# Django 1.6
|
||||
return super(DatabaseWrapper, self)._cursor()
|
||||
except AttributeError:
|
||||
if not self.connection:
|
||||
self._connect()
|
||||
return self.create_cursor()
|
||||
|
||||
def get_server_version(self):
|
||||
"""Returns the MySQL server version of current connection
|
||||
|
||||
Returns a tuple
|
||||
"""
|
||||
try:
|
||||
# Django 1.6
|
||||
self.ensure_connection()
|
||||
except AttributeError:
|
||||
if not self.connection:
|
||||
self._connect()
|
||||
|
||||
return self.connection.get_server_version()
|
||||
|
||||
def disable_constraint_checking(self):
|
||||
"""Disables foreign key checks
|
||||
|
||||
Disables foreign key checks, primarily for use in adding rows with
|
||||
forward references. Always returns True,
|
||||
to indicate constraint checks need to be re-enabled.
|
||||
|
||||
Returns True
|
||||
"""
|
||||
self.cursor().execute('SET @@session.foreign_key_checks = 0')
|
||||
return True
|
||||
|
||||
def enable_constraint_checking(self):
|
||||
"""Re-enable foreign key checks
|
||||
|
||||
Re-enable foreign key checks after they have been disabled.
|
||||
"""
|
||||
# Override needs_rollback in case constraint_checks_disabled is
|
||||
# nested inside transaction.atomic.
|
||||
if django.VERSION >= (1, 6):
|
||||
self.needs_rollback, needs_rollback = False, self.needs_rollback
|
||||
try:
|
||||
self.cursor().execute('SET @@session.foreign_key_checks = 1')
|
||||
finally:
|
||||
if django.VERSION >= (1, 6):
|
||||
self.needs_rollback = needs_rollback
|
||||
|
||||
def check_constraints(self, table_names=None):
|
||||
"""Check rows in tables for invalid foreign key references
|
||||
|
||||
Checks each table name in `table_names` for rows with invalid foreign
|
||||
key references. This method is intended to be used in conjunction with
|
||||
`disable_constraint_checking()` and `enable_constraint_checking()`, to
|
||||
determine if rows with invalid references were entered while
|
||||
constraint checks were off.
|
||||
|
||||
Raises an IntegrityError on the first invalid foreign key reference
|
||||
encountered (if any) and provides detailed information about the
|
||||
invalid reference in the error message.
|
||||
|
||||
Backends can override this method if they can more directly apply
|
||||
constraint checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE")
|
||||
"""
|
||||
ref_query = """
|
||||
SELECT REFERRING.`{0}`, REFERRING.`{1}` FROM `{2}` as REFERRING
|
||||
LEFT JOIN `{3}` as REFERRED
|
||||
ON (REFERRING.`{4}` = REFERRED.`{5}`)
|
||||
WHERE REFERRING.`{6}` IS NOT NULL AND REFERRED.`{7}` IS NULL"""
|
||||
cursor = self.cursor()
|
||||
if table_names is None:
|
||||
table_names = self.introspection.table_names(cursor)
|
||||
for table_name in table_names:
|
||||
primary_key_column_name = \
|
||||
self.introspection.get_primary_key_column(cursor, table_name)
|
||||
if not primary_key_column_name:
|
||||
continue
|
||||
key_columns = self.introspection.get_key_columns(cursor,
|
||||
table_name)
|
||||
for column_name, referenced_table_name, referenced_column_name \
|
||||
in key_columns:
|
||||
cursor.execute(ref_query.format(primary_key_column_name,
|
||||
column_name, table_name,
|
||||
referenced_table_name,
|
||||
column_name,
|
||||
referenced_column_name,
|
||||
column_name,
|
||||
referenced_column_name))
|
||||
for bad_row in cursor.fetchall():
|
||||
msg = ("The row in table '{0}' with primary key '{1}' has "
|
||||
"an invalid foreign key: {2}.{3} contains a value "
|
||||
"'{4}' that does not have a corresponding value in "
|
||||
"{5}.{6}.".format(table_name, bad_row[0],
|
||||
table_name, column_name,
|
||||
bad_row[1], referenced_table_name,
|
||||
referenced_column_name))
|
||||
raise utils.IntegrityError(msg)
|
||||
|
||||
def _rollback(self):
|
||||
try:
|
||||
BaseDatabaseWrapper._rollback(self)
|
||||
except NotSupportedError:
|
||||
pass
|
||||
|
||||
def _set_autocommit(self, autocommit):
|
||||
# Django 1.6
|
||||
with self.wrap_database_errors:
|
||||
self.connection.autocommit = autocommit
|
||||
|
||||
def schema_editor(self, *args, **kwargs):
|
||||
"""Returns a new instance of this backend's SchemaEditor"""
|
||||
# Django 1.7
|
||||
return DatabaseSchemaEditor(self, *args, **kwargs)
|
||||
|
||||
def is_usable(self):
|
||||
# Django 1.6
|
||||
return self.connection.is_connected()
|
||||
|
||||
@cached_property
|
||||
def mysql_version(self):
|
||||
config = self.get_connection_params()
|
||||
temp_conn = mysql.connector.connect(**config)
|
||||
server_version = temp_conn.get_server_version()
|
||||
temp_conn.close()
|
||||
|
||||
return server_version
|
||||
|
||||
@property
|
||||
def use_pure(self):
|
||||
return not HAVE_CEXT or self._use_pure
|
|
@ -0,0 +1,57 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
import django
|
||||
import subprocess
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.client import BaseDatabaseClient
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseClient
|
||||
|
||||
|
||||
class DatabaseClient(BaseDatabaseClient):
|
||||
executable_name = 'mysql'
|
||||
|
||||
@classmethod
|
||||
def settings_to_cmd_args(cls, settings_dict):
|
||||
args = [cls.executable_name]
|
||||
|
||||
db = settings_dict['OPTIONS'].get('database', settings_dict['NAME'])
|
||||
user = settings_dict['OPTIONS'].get('user',
|
||||
settings_dict['USER'])
|
||||
passwd = settings_dict['OPTIONS'].get('password',
|
||||
settings_dict['PASSWORD'])
|
||||
host = settings_dict['OPTIONS'].get('host', settings_dict['HOST'])
|
||||
port = settings_dict['OPTIONS'].get('port', settings_dict['PORT'])
|
||||
defaults_file = settings_dict['OPTIONS'].get('read_default_file')
|
||||
|
||||
# --defaults-file should always be the first option
|
||||
if defaults_file:
|
||||
args.append("--defaults-file={0}".format(defaults_file))
|
||||
|
||||
# We force SQL_MODE to TRADITIONAL
|
||||
args.append("--init-command=SET @@session.SQL_MODE=TRADITIONAL")
|
||||
|
||||
if user:
|
||||
args.append("--user={0}".format(user))
|
||||
if passwd:
|
||||
args.append("--password={0}".format(passwd))
|
||||
|
||||
if host:
|
||||
if '/' in host:
|
||||
args.append("--socket={0}".format(host))
|
||||
else:
|
||||
args.append("--host={0}".format(host))
|
||||
|
||||
if port:
|
||||
args.append("--port={0}".format(port))
|
||||
|
||||
if db:
|
||||
args.append("--database={0}".format(db))
|
||||
|
||||
return args
|
||||
|
||||
def runshell(self):
|
||||
args = DatabaseClient.settings_to_cmd_args(
|
||||
self.connection.settings_dict)
|
||||
subprocess.call(args)
|
|
@ -0,0 +1,59 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
|
||||
import django
|
||||
from django.db.models.sql import compiler
|
||||
from django.utils.six.moves import zip_longest
|
||||
|
||||
|
||||
class SQLCompiler(compiler.SQLCompiler):
|
||||
def resolve_columns(self, row, fields=()):
|
||||
values = []
|
||||
index_extra_select = len(self.query.extra_select)
|
||||
bool_fields = ("BooleanField", "NullBooleanField")
|
||||
for value, field in zip_longest(row[index_extra_select:], fields):
|
||||
if (field and field.get_internal_type() in bool_fields and
|
||||
value in (0, 1)):
|
||||
value = bool(value)
|
||||
values.append(value)
|
||||
return row[:index_extra_select] + tuple(values)
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
def as_subquery_condition(self, alias, columns, compiler):
|
||||
qn = compiler.quote_name_unless_alias
|
||||
qn2 = self.connection.ops.quote_name
|
||||
sql, params = self.as_sql()
|
||||
return '(%s) IN (%s)' % (', '.join('%s.%s' % (qn(alias), qn2(column)) for column in columns), sql), params
|
||||
else:
|
||||
def as_subquery_condition(self, alias, columns, qn):
|
||||
# Django 1.6
|
||||
qn2 = self.connection.ops.quote_name
|
||||
sql, params = self.as_sql()
|
||||
column_list = ', '.join(
|
||||
['%s.%s' % (qn(alias), qn2(column)) for column in columns])
|
||||
return '({0}) IN ({1})'.format(column_list, sql), params
|
||||
|
||||
|
||||
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
if django.VERSION < (1, 8):
|
||||
class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
if django.VERSION >= (1, 6):
|
||||
class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler):
|
||||
# Django 1.6
|
||||
pass
|
|
@ -0,0 +1,141 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
import django
|
||||
from django.db import models
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.creation import BaseDatabaseCreation
|
||||
else:
|
||||
from django.db.backends.creation import BaseDatabaseCreation
|
||||
if django.VERSION < (1, 7):
|
||||
from django.db.backends.util import truncate_name
|
||||
else:
|
||||
from django.db.backends.utils import truncate_name
|
||||
|
||||
class DatabaseCreation(BaseDatabaseCreation):
|
||||
"""Maps Django Field object with MySQL data types
|
||||
"""
|
||||
def __init__(self, connection):
|
||||
super(DatabaseCreation, self).__init__(connection)
|
||||
|
||||
if django.VERSION < (1, 8):
|
||||
self.data_types = {
|
||||
'AutoField': 'integer AUTO_INCREMENT',
|
||||
'BinaryField': 'longblob',
|
||||
'BooleanField': 'bool',
|
||||
'CharField': 'varchar(%(max_length)s)',
|
||||
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
|
||||
'DateField': 'date',
|
||||
'DateTimeField': 'datetime', # ms support set later
|
||||
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'FileField': 'varchar(%(max_length)s)',
|
||||
'FilePathField': 'varchar(%(max_length)s)',
|
||||
'FloatField': 'double precision',
|
||||
'IntegerField': 'integer',
|
||||
'BigIntegerField': 'bigint',
|
||||
'IPAddressField': 'char(15)',
|
||||
|
||||
'GenericIPAddressField': 'char(39)',
|
||||
'NullBooleanField': 'bool',
|
||||
'OneToOneField': 'integer',
|
||||
'PositiveIntegerField': 'integer UNSIGNED',
|
||||
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||
'SlugField': 'varchar(%(max_length)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'longtext',
|
||||
'TimeField': 'time', # ms support set later
|
||||
}
|
||||
|
||||
# Support for microseconds
|
||||
if self.connection.mysql_version >= (5, 6, 4):
|
||||
self.data_types.update({
|
||||
'DateTimeField': 'datetime(6)',
|
||||
'TimeField': 'time(6)',
|
||||
})
|
||||
|
||||
def sql_table_creation_suffix(self):
|
||||
suffix = []
|
||||
if django.VERSION < (1, 7):
|
||||
if self.connection.settings_dict['TEST_CHARSET']:
|
||||
suffix.append('CHARACTER SET {0}'.format(
|
||||
self.connection.settings_dict['TEST_CHARSET']))
|
||||
if self.connection.settings_dict['TEST_COLLATION']:
|
||||
suffix.append('COLLATE {0}'.format(
|
||||
self.connection.settings_dict['TEST_COLLATION']))
|
||||
|
||||
else:
|
||||
test_settings = self.connection.settings_dict['TEST']
|
||||
if test_settings['CHARSET']:
|
||||
suffix.append('CHARACTER SET %s' % test_settings['CHARSET'])
|
||||
if test_settings['COLLATION']:
|
||||
suffix.append('COLLATE %s' % test_settings['COLLATION'])
|
||||
|
||||
return ' '.join(suffix)
|
||||
|
||||
if django.VERSION < (1, 6):
|
||||
def sql_for_inline_foreign_key_references(self, field, known_models,
|
||||
style):
|
||||
"All inline references are pending under MySQL"
|
||||
return [], True
|
||||
else:
|
||||
def sql_for_inline_foreign_key_references(self, model, field,
|
||||
known_models, style):
|
||||
"All inline references are pending under MySQL"
|
||||
return [], True
|
||||
|
||||
def sql_for_inline_many_to_many_references(self, model, field, style):
|
||||
opts = model._meta
|
||||
qn = self.connection.ops.quote_name
|
||||
|
||||
columndef = ' {column} {type} {options},'
|
||||
table_output = [
|
||||
columndef.format(
|
||||
column=style.SQL_FIELD(qn(field.m2m_column_name())),
|
||||
type=style.SQL_COLTYPE(models.ForeignKey(model).db_type(
|
||||
connection=self.connection)),
|
||||
options=style.SQL_KEYWORD('NOT NULL')
|
||||
),
|
||||
columndef.format(
|
||||
column=style.SQL_FIELD(qn(field.m2m_reverse_name())),
|
||||
type=style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(
|
||||
connection=self.connection)),
|
||||
options=style.SQL_KEYWORD('NOT NULL')
|
||||
),
|
||||
]
|
||||
|
||||
deferred = [
|
||||
(field.m2m_db_table(), field.m2m_column_name(), opts.db_table,
|
||||
opts.pk.column),
|
||||
(field.m2m_db_table(), field.m2m_reverse_name(),
|
||||
field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
|
||||
]
|
||||
return table_output, deferred
|
||||
|
||||
def sql_destroy_indexes_for_fields(self, model, fields, style):
|
||||
# Django 1.6
|
||||
if len(fields) == 1 and fields[0].db_tablespace:
|
||||
tablespace_sql = self.connection.ops.tablespace_sql(
|
||||
fields[0].db_tablespace)
|
||||
elif model._meta.db_tablespace:
|
||||
tablespace_sql = self.connection.ops.tablespace_sql(
|
||||
model._meta.db_tablespace)
|
||||
else:
|
||||
tablespace_sql = ""
|
||||
if tablespace_sql:
|
||||
tablespace_sql = " " + tablespace_sql
|
||||
|
||||
field_names = []
|
||||
qn = self.connection.ops.quote_name
|
||||
for f in fields:
|
||||
field_names.append(style.SQL_FIELD(qn(f.column)))
|
||||
|
||||
index_name = "{0}_{1}".format(model._meta.db_table,
|
||||
self._digest([f.name for f in fields]))
|
||||
|
||||
return [
|
||||
style.SQL_KEYWORD("DROP INDEX") + " " +
|
||||
style.SQL_TABLE(qn(truncate_name(index_name,
|
||||
self.connection.ops.max_name_length()))) + " " +
|
||||
style.SQL_KEYWORD("ON") + " " +
|
||||
style.SQL_TABLE(qn(model._meta.db_table)) + ";",
|
||||
]
|
|
@ -0,0 +1,127 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
# New file added for Django 1.8
|
||||
|
||||
import django
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.features import BaseDatabaseFeatures
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseFeatures
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils import six
|
||||
|
||||
try:
|
||||
import pytz
|
||||
HAVE_PYTZ = True
|
||||
except ImportError:
|
||||
HAVE_PYTZ = False
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
"""Features specific to MySQL
|
||||
|
||||
Microsecond precision is supported since MySQL 5.6.3 and turned on
|
||||
by default if this MySQL version is used.
|
||||
"""
|
||||
empty_fetchmany_value = []
|
||||
update_can_self_select = False
|
||||
allows_group_by_pk = True
|
||||
related_fields_match_type = True
|
||||
allow_sliced_subqueries = False
|
||||
has_bulk_insert = True
|
||||
has_select_for_update = True
|
||||
has_select_for_update_nowait = False
|
||||
supports_forward_references = False
|
||||
supports_regex_backreferencing = False
|
||||
supports_date_lookup_using_string = False
|
||||
can_introspect_autofield = True
|
||||
can_introspect_binary_field = False
|
||||
can_introspect_small_integer_field = True
|
||||
supports_timezones = False
|
||||
requires_explicit_null_ordering_when_grouping = True
|
||||
allows_auto_pk_0 = False
|
||||
allows_primary_key_0 = False
|
||||
uses_savepoints = True
|
||||
atomic_transactions = False
|
||||
supports_column_check_constraints = False
|
||||
|
||||
if django.VERSION < (1, 8):
|
||||
supports_long_model_names = False
|
||||
supports_binary_field = six.PY2
|
||||
can_introspect_boolean_field = False
|
||||
|
||||
def __init__(self, connection):
|
||||
super(DatabaseFeatures, self).__init__(connection)
|
||||
|
||||
@cached_property
|
||||
def supports_microsecond_precision(self):
|
||||
if self.connection.mysql_version >= (5, 6, 3):
|
||||
return True
|
||||
return False
|
||||
|
||||
@cached_property
|
||||
def mysql_storage_engine(self):
|
||||
"""Get default storage engine of MySQL
|
||||
|
||||
This method creates a table without ENGINE table option and inspects
|
||||
which engine was used.
|
||||
|
||||
Used by Django tests.
|
||||
"""
|
||||
tblname = 'INTROSPECT_TEST'
|
||||
|
||||
droptable = 'DROP TABLE IF EXISTS {table}'.format(table=tblname)
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute(droptable)
|
||||
cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname))
|
||||
|
||||
if self.connection.mysql_version >= (5, 0, 0):
|
||||
cursor.execute(
|
||||
"SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
|
||||
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
|
||||
(self.connection.settings_dict['NAME'], tblname))
|
||||
engine = cursor.fetchone()[0]
|
||||
else:
|
||||
# Very old MySQL servers..
|
||||
cursor.execute("SHOW TABLE STATUS WHERE Name='{table}'".format(
|
||||
table=tblname))
|
||||
engine = cursor.fetchone()[1]
|
||||
cursor.execute(droptable)
|
||||
|
||||
self._cached_storage_engine = engine
|
||||
return engine
|
||||
|
||||
@cached_property
|
||||
def _disabled_supports_transactions(self):
|
||||
return self.mysql_storage_engine == 'InnoDB'
|
||||
|
||||
@cached_property
|
||||
def can_introspect_foreign_keys(self):
|
||||
"""Confirm support for introspected foreign keys
|
||||
|
||||
Only the InnoDB storage engine supports Foreigen Key (not taking
|
||||
into account MySQL Cluster here).
|
||||
"""
|
||||
return self.mysql_storage_engine == 'InnoDB'
|
||||
|
||||
@cached_property
|
||||
def has_zoneinfo_database(self):
|
||||
"""Tests if the time zone definitions are installed
|
||||
|
||||
MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects
|
||||
abbreviations (eg. EAT). When pytz isn't installed and the current
|
||||
time zone is LocalTimezone (the only sensible value in this context),
|
||||
the current time zone name will be an abbreviation. As a consequence,
|
||||
MySQL cannot perform time zone conversions reliably.
|
||||
"""
|
||||
# Django 1.6
|
||||
if not HAVE_PYTZ:
|
||||
return False
|
||||
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
|
||||
return cursor.fetchall() != []
|
||||
|
||||
def introspected_boolean_field_type(self, *args, **kwargs):
|
||||
# New in Django 1.8
|
||||
return 'IntegerField'
|
|
@ -0,0 +1,322 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
import django
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.introspection import (
|
||||
BaseDatabaseIntrospection, FieldInfo, TableInfo
|
||||
)
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseIntrospection
|
||||
|
||||
if django.VERSION >= (1, 6):
|
||||
if django.VERSION < (1, 8):
|
||||
from django.db.backends import FieldInfo
|
||||
from django.utils.encoding import force_text
|
||||
if django.VERSION >= (1, 7):
|
||||
from django.utils.datastructures import OrderedSet
|
||||
|
||||
from mysql.connector.constants import FieldType
|
||||
|
||||
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) "
|
||||
r"REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('extra',))
|
||||
|
||||
|
||||
class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||
data_types_reverse = {
|
||||
FieldType.BLOB: 'TextField',
|
||||
FieldType.DECIMAL: 'DecimalField',
|
||||
FieldType.NEWDECIMAL: 'DecimalField',
|
||||
FieldType.DATE: 'DateField',
|
||||
FieldType.DATETIME: 'DateTimeField',
|
||||
FieldType.DOUBLE: 'FloatField',
|
||||
FieldType.FLOAT: 'FloatField',
|
||||
FieldType.INT24: 'IntegerField',
|
||||
FieldType.LONG: 'IntegerField',
|
||||
FieldType.LONGLONG: 'BigIntegerField',
|
||||
FieldType.SHORT: (
|
||||
'IntegerField' if django.VERSION < (1, 8) else 'SmallIntegerField'
|
||||
),
|
||||
FieldType.STRING: 'CharField',
|
||||
FieldType.TIME: 'TimeField',
|
||||
FieldType.TIMESTAMP: 'DateTimeField',
|
||||
FieldType.TINY: 'IntegerField',
|
||||
FieldType.TINY_BLOB: 'TextField',
|
||||
FieldType.MEDIUM_BLOB: 'TextField',
|
||||
FieldType.LONG_BLOB: 'TextField',
|
||||
FieldType.VAR_STRING: 'CharField',
|
||||
}
|
||||
|
||||
def get_field_type(self, data_type, description):
|
||||
field_type = super(DatabaseIntrospection, self).get_field_type(
|
||||
data_type, description)
|
||||
if (field_type == 'IntegerField'
|
||||
and 'auto_increment' in description.extra):
|
||||
return 'AutoField'
|
||||
return field_type
|
||||
|
||||
def get_table_list(self, cursor):
|
||||
"""Returns a list of table names in the current database."""
|
||||
cursor.execute("SHOW FULL TABLES")
|
||||
if django.VERSION >= (1, 8):
|
||||
return [
|
||||
TableInfo(row[0], {'BASE TABLE': 't', 'VIEW': 'v'}.get(row[1]))
|
||||
for row in cursor.fetchall()
|
||||
]
|
||||
else:
|
||||
return [row[0] for row in cursor.fetchall()]
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
def get_table_description(self, cursor, table_name):
|
||||
"""
|
||||
Returns a description of the table, with the DB-API
|
||||
cursor.description interface."
|
||||
"""
|
||||
# - information_schema database gives more accurate results for
|
||||
# some figures:
|
||||
# - varchar length returned by cursor.description is an internal
|
||||
# length, not visible length (#5725)
|
||||
# - precision and scale (for decimal fields) (#5014)
|
||||
# - auto_increment is not available in cursor.description
|
||||
InfoLine = namedtuple(
|
||||
'InfoLine',
|
||||
'col_name data_type max_len num_prec num_scale extra'
|
||||
)
|
||||
cursor.execute("""
|
||||
SELECT column_name, data_type, character_maximum_length,
|
||||
numeric_precision, numeric_scale, extra
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = %s AND table_schema = DATABASE()""",
|
||||
[table_name])
|
||||
field_info = dict(
|
||||
(line[0], InfoLine(*line)) for line in cursor.fetchall()
|
||||
)
|
||||
|
||||
cursor.execute("SELECT * FROM %s LIMIT 1"
|
||||
% self.connection.ops.quote_name(table_name))
|
||||
to_int = lambda i: int(i) if i is not None else i
|
||||
fields = []
|
||||
for line in cursor.description:
|
||||
col_name = force_text(line[0])
|
||||
fields.append(
|
||||
FieldInfo(*((col_name,)
|
||||
+ line[1:3]
|
||||
+ (to_int(field_info[col_name].max_len)
|
||||
or line[3],
|
||||
to_int(field_info[col_name].num_prec)
|
||||
or line[4],
|
||||
to_int(field_info[col_name].num_scale)
|
||||
or line[5])
|
||||
+ (line[6],)
|
||||
+ (field_info[col_name].extra,)))
|
||||
)
|
||||
return fields
|
||||
else:
|
||||
def get_table_description(self, cursor, table_name):
|
||||
"""
|
||||
Returns a description of the table, with the DB-API
|
||||
cursor.description interface.
|
||||
"""
|
||||
# varchar length returned by cursor.description is an internal
|
||||
# length not visible length (#5725), use information_schema database
|
||||
# to fix this
|
||||
cursor.execute(
|
||||
"SELECT column_name, character_maximum_length "
|
||||
"FROM INFORMATION_SCHEMA.COLUMNS "
|
||||
"WHERE table_name = %s AND table_schema = DATABASE() "
|
||||
"AND character_maximum_length IS NOT NULL", [table_name])
|
||||
length_map = dict(cursor.fetchall())
|
||||
|
||||
# Also getting precision and scale from
|
||||
# information_schema (see #5014)
|
||||
cursor.execute(
|
||||
"SELECT column_name, numeric_precision, numeric_scale FROM "
|
||||
"INFORMATION_SCHEMA.COLUMNS WHERE table_name = %s AND "
|
||||
"table_schema = DATABASE() AND data_type='decimal'",
|
||||
[table_name])
|
||||
numeric_map = dict((line[0], tuple([int(n) for n in line[1:]]))
|
||||
for line in cursor.fetchall())
|
||||
|
||||
cursor.execute("SELECT * FROM {0} LIMIT 1".format(
|
||||
self.connection.ops.quote_name(table_name)))
|
||||
|
||||
if django.VERSION >= (1, 6):
|
||||
return [FieldInfo(*((force_text(line[0]),)
|
||||
+ line[1:3]
|
||||
+ (length_map.get(line[0], line[3]),)
|
||||
+ numeric_map.get(line[0], line[4:6])
|
||||
+ (line[6],)))
|
||||
for line in cursor.description]
|
||||
else:
|
||||
return [
|
||||
line[:3] + (length_map.get(line[0], line[3]),) + line[4:]
|
||||
for line in cursor.description
|
||||
]
|
||||
|
||||
def _name_to_index(self, cursor, table_name):
|
||||
"""
|
||||
Returns a dictionary of {field_name: field_index} for the given table.
|
||||
Indexes are 0-based.
|
||||
"""
|
||||
return dict((d[0], i) for i, d in enumerate(
|
||||
self.get_table_description(cursor, table_name)))
|
||||
|
||||
def get_relations(self, cursor, table_name):
|
||||
"""
|
||||
Returns a dictionary of {field_index: (field_index_other_table,
|
||||
other_table)}
|
||||
representing all relationships to the given table. Indexes are 0-based.
|
||||
"""
|
||||
constraints = self.get_key_columns(cursor, table_name)
|
||||
relations = {}
|
||||
if django.VERSION >= (1, 8):
|
||||
for my_fieldname, other_table, other_field in constraints:
|
||||
relations[my_fieldname] = (other_field, other_table)
|
||||
return relations
|
||||
else:
|
||||
my_field_dict = self._name_to_index(cursor, table_name)
|
||||
for my_fieldname, other_table, other_field in constraints:
|
||||
other_field_index = self._name_to_index(
|
||||
cursor, other_table)[other_field]
|
||||
my_field_index = my_field_dict[my_fieldname]
|
||||
relations[my_field_index] = (other_field_index, other_table)
|
||||
return relations
|
||||
|
||||
def get_key_columns(self, cursor, table_name):
|
||||
"""
|
||||
Returns a list of (column_name, referenced_table_name,
|
||||
referenced_column_name) for all key columns in given table.
|
||||
"""
|
||||
key_columns = []
|
||||
cursor.execute(
|
||||
"SELECT column_name, referenced_table_name, referenced_column_name "
|
||||
"FROM information_schema.key_column_usage "
|
||||
"WHERE table_name = %s "
|
||||
"AND table_schema = DATABASE() "
|
||||
"AND referenced_table_name IS NOT NULL "
|
||||
"AND referenced_column_name IS NOT NULL", [table_name])
|
||||
key_columns.extend(cursor.fetchall())
|
||||
return key_columns
|
||||
|
||||
def get_indexes(self, cursor, table_name):
|
||||
cursor.execute("SHOW INDEX FROM {0}"
|
||||
"".format(self.connection.ops.quote_name(table_name)))
|
||||
# Do a two-pass search for indexes: on first pass check which indexes
|
||||
# are multicolumn, on second pass check which single-column indexes
|
||||
# are present.
|
||||
rows = list(cursor.fetchall())
|
||||
multicol_indexes = set()
|
||||
for row in rows:
|
||||
if row[3] > 1:
|
||||
multicol_indexes.add(row[2])
|
||||
indexes = {}
|
||||
for row in rows:
|
||||
if row[2] in multicol_indexes:
|
||||
continue
|
||||
if row[4] not in indexes:
|
||||
indexes[row[4]] = {'primary_key': False, 'unique': False}
|
||||
# It's possible to have the unique and PK constraints in
|
||||
# separate indexes.
|
||||
if row[2] == 'PRIMARY':
|
||||
indexes[row[4]]['primary_key'] = True
|
||||
if not row[1]:
|
||||
indexes[row[4]]['unique'] = True
|
||||
return indexes
|
||||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Returns the name of the primary key column for the given table
|
||||
"""
|
||||
# Django 1.6
|
||||
for column in self.get_indexes(cursor, table_name).items():
|
||||
if column[1]['primary_key']:
|
||||
return column[0]
|
||||
return None
|
||||
|
||||
def get_storage_engine(self, cursor, table_name):
|
||||
"""
|
||||
Retrieves the storage engine for a given table. Returns the default
|
||||
storage engine if the table doesn't exist.
|
||||
"""
|
||||
cursor.execute(
|
||||
"SELECT engine "
|
||||
"FROM information_schema.tables "
|
||||
"WHERE table_name = %s", [table_name])
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
return self.connection.features.mysql_storage_engine
|
||||
return result[0]
|
||||
|
||||
def get_constraints(self, cursor, table_name):
|
||||
"""
|
||||
Retrieves any constraints or keys (unique, pk, fk, check, index) across
|
||||
one or more columns.
|
||||
"""
|
||||
# Django 1.7
|
||||
constraints = {}
|
||||
# Get the actual constraint names and columns
|
||||
name_query = (
|
||||
"SELECT kc.`constraint_name`, kc.`column_name`, "
|
||||
"kc.`referenced_table_name`, kc.`referenced_column_name` "
|
||||
"FROM information_schema.key_column_usage AS kc "
|
||||
"WHERE "
|
||||
"kc.table_schema = %s AND "
|
||||
"kc.table_name = %s"
|
||||
)
|
||||
cursor.execute(name_query, [self.connection.settings_dict['NAME'],
|
||||
table_name])
|
||||
for constraint, column, ref_table, ref_column in cursor.fetchall():
|
||||
if constraint not in constraints:
|
||||
constraints[constraint] = {
|
||||
'columns': OrderedSet(),
|
||||
'primary_key': False,
|
||||
'unique': False,
|
||||
'index': False,
|
||||
'check': False,
|
||||
'foreign_key': (
|
||||
(ref_table, ref_column) if ref_column else None,
|
||||
)
|
||||
}
|
||||
constraints[constraint]['columns'].add(column)
|
||||
# Now get the constraint types
|
||||
type_query = """
|
||||
SELECT c.constraint_name, c.constraint_type
|
||||
FROM information_schema.table_constraints AS c
|
||||
WHERE
|
||||
c.table_schema = %s AND
|
||||
c.table_name = %s
|
||||
"""
|
||||
cursor.execute(type_query, [self.connection.settings_dict['NAME'],
|
||||
table_name])
|
||||
for constraint, kind in cursor.fetchall():
|
||||
if kind.lower() == "primary key":
|
||||
constraints[constraint]['primary_key'] = True
|
||||
constraints[constraint]['unique'] = True
|
||||
elif kind.lower() == "unique":
|
||||
constraints[constraint]['unique'] = True
|
||||
# Now add in the indexes
|
||||
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(
|
||||
table_name))
|
||||
for table, non_unique, index, colseq, column in [x[:5] for x in
|
||||
cursor.fetchall()]:
|
||||
if index not in constraints:
|
||||
constraints[index] = {
|
||||
'columns': OrderedSet(),
|
||||
'primary_key': False,
|
||||
'unique': False,
|
||||
'index': True,
|
||||
'check': False,
|
||||
'foreign_key': None,
|
||||
}
|
||||
constraints[index]['index'] = True
|
||||
constraints[index]['columns'].add(column)
|
||||
# Convert the sorted sets to lists
|
||||
for constraint in constraints.values():
|
||||
constraint['columns'] = list(constraint['columns'])
|
||||
return constraints
|
|
@ -0,0 +1,308 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
# New file added for Django 1.8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.operations import BaseDatabaseOperations
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseOperations
|
||||
from django.utils import six, timezone
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
try:
|
||||
from _mysql_connector import datetime_to_mysql, time_to_mysql
|
||||
except ImportError:
|
||||
HAVE_CEXT = False
|
||||
else:
|
||||
HAVE_CEXT = True
|
||||
|
||||
|
||||
class DatabaseOperations(BaseDatabaseOperations):
|
||||
compiler_module = "mysql.connector.django.compiler"
|
||||
|
||||
# MySQL stores positive fields as UNSIGNED ints.
|
||||
if django.VERSION >= (1, 7):
|
||||
integer_field_ranges = dict(BaseDatabaseOperations.integer_field_ranges,
|
||||
PositiveSmallIntegerField=(0, 4294967295),
|
||||
PositiveIntegerField=(
|
||||
0, 18446744073709551615),)
|
||||
|
||||
def date_extract_sql(self, lookup_type, field_name):
|
||||
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||
if lookup_type == 'week_day':
|
||||
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
|
||||
# Note: WEEKDAY() returns 0-6, Monday=0.
|
||||
return "DAYOFWEEK({0})".format(field_name)
|
||||
else:
|
||||
return "EXTRACT({0} FROM {1})".format(
|
||||
lookup_type.upper(), field_name)
|
||||
|
||||
def date_trunc_sql(self, lookup_type, field_name):
|
||||
"""Returns SQL simulating DATE_TRUNC
|
||||
|
||||
This function uses MySQL functions DATE_FORMAT and CAST to
|
||||
simulate DATE_TRUNC.
|
||||
|
||||
The field_name is returned when lookup_type is not supported.
|
||||
"""
|
||||
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||
format = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S')
|
||||
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
|
||||
try:
|
||||
i = fields.index(lookup_type) + 1
|
||||
except ValueError:
|
||||
# Wrong lookup type, just return the value from MySQL as-is
|
||||
sql = field_name
|
||||
else:
|
||||
format_str = ''.join([f for f in format[:i]] +
|
||||
[f for f in format_def[i:]])
|
||||
sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format(
|
||||
field_name, format_str)
|
||||
return sql
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
# Django 1.6
|
||||
if settings.USE_TZ:
|
||||
field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name)
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
|
||||
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||
if lookup_type == 'week_day':
|
||||
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
|
||||
# Note: WEEKDAY() returns 0-6, Monday=0.
|
||||
sql = "DAYOFWEEK({0})".format(field_name)
|
||||
else:
|
||||
sql = "EXTRACT({0} FROM {1})".format(lookup_type.upper(),
|
||||
field_name)
|
||||
return sql, params
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
# Django 1.6
|
||||
if settings.USE_TZ:
|
||||
field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name)
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||
format_ = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S')
|
||||
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
|
||||
try:
|
||||
i = fields.index(lookup_type) + 1
|
||||
except ValueError:
|
||||
sql = field_name
|
||||
else:
|
||||
format_str = ''.join([f for f in format_[:i]] +
|
||||
[f for f in format_def[i:]])
|
||||
sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format(
|
||||
field_name, format_str)
|
||||
return sql, params
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
def date_interval_sql(self, timedelta):
|
||||
"""Returns SQL for calculating date/time intervals
|
||||
"""
|
||||
return "INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND" % (
|
||||
timedelta.days, timedelta.seconds, timedelta.microseconds), []
|
||||
else:
|
||||
def date_interval_sql(self, sql, connector, timedelta):
|
||||
"""Returns SQL for calculating date/time intervals
|
||||
"""
|
||||
fmt = (
|
||||
"({sql} {connector} INTERVAL '{days} "
|
||||
"0:0:{secs}:{msecs}' DAY_MICROSECOND)"
|
||||
)
|
||||
return fmt.format(
|
||||
sql=sql,
|
||||
connector=connector,
|
||||
days=timedelta.days,
|
||||
secs=timedelta.seconds,
|
||||
msecs=timedelta.microseconds
|
||||
)
|
||||
|
||||
def format_for_duration_arithmetic(self, sql):
|
||||
if self.connection.features.supports_microsecond_precision:
|
||||
return 'INTERVAL %s MICROSECOND' % sql
|
||||
else:
|
||||
return 'INTERVAL FLOOR(%s / 1000000) SECOND' % sql
|
||||
|
||||
def drop_foreignkey_sql(self):
|
||||
return "DROP FOREIGN KEY"
|
||||
|
||||
def force_no_ordering(self):
|
||||
"""
|
||||
"ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
|
||||
columns. If no ordering would otherwise be applied, we don't want any
|
||||
implicit sorting going on.
|
||||
"""
|
||||
if django.VERSION >= (1, 8):
|
||||
return [(None, ("NULL", [], False))]
|
||||
else:
|
||||
return ["NULL"]
|
||||
|
||||
def fulltext_search_sql(self, field_name):
|
||||
return 'MATCH ({0}) AGAINST (%s IN BOOLEAN MODE)'.format(field_name)
|
||||
|
||||
def last_executed_query(self, cursor, sql, params):
|
||||
return force_text(cursor.statement, errors='replace')
|
||||
|
||||
def no_limit_value(self):
|
||||
# 2**64 - 1, as recommended by the MySQL documentation
|
||||
return 18446744073709551615
|
||||
|
||||
def quote_name(self, name):
|
||||
if name.startswith("`") and name.endswith("`"):
|
||||
return name # Quoting once is enough.
|
||||
return "`{0}`".format(name)
|
||||
|
||||
def random_function_sql(self):
|
||||
return 'RAND()'
|
||||
|
||||
def sql_flush(self, style, tables, sequences, allow_cascade=False):
|
||||
if tables:
|
||||
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
|
||||
for table in tables:
|
||||
sql.append('{keyword} {table};'.format(
|
||||
keyword=style.SQL_KEYWORD('TRUNCATE'),
|
||||
table=style.SQL_FIELD(self.quote_name(table))))
|
||||
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
|
||||
sql.extend(self.sequence_reset_by_name_sql(style, sequences))
|
||||
return sql
|
||||
else:
|
||||
return []
|
||||
|
||||
def validate_autopk_value(self, value):
|
||||
# MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653.
|
||||
if value == 0:
|
||||
raise ValueError('The database backend does not accept 0 as a '
|
||||
'value for AutoField.')
|
||||
return value
|
||||
|
||||
def value_to_db_datetime(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
# MySQL doesn't support tz-aware times
|
||||
if timezone.is_aware(value):
|
||||
if settings.USE_TZ:
|
||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
else:
|
||||
raise ValueError(
|
||||
"MySQL backend does not support timezone-aware times."
|
||||
)
|
||||
if not self.connection.features.supports_microsecond_precision:
|
||||
value = value.replace(microsecond=0)
|
||||
if not self.connection.use_pure:
|
||||
return datetime_to_mysql(value)
|
||||
return self.connection.converter.to_mysql(value)
|
||||
|
||||
def value_to_db_time(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# MySQL doesn't support tz-aware times
|
||||
if timezone.is_aware(value):
|
||||
raise ValueError("MySQL backend does not support timezone-aware "
|
||||
"times.")
|
||||
|
||||
if not self.connection.use_pure:
|
||||
return time_to_mysql(value)
|
||||
return self.connection.converter.to_mysql(value)
|
||||
|
||||
def max_name_length(self):
|
||||
return 64
|
||||
|
||||
def bulk_insert_sql(self, fields, num_values):
|
||||
items_sql = "({0})".format(", ".join(["%s"] * len(fields)))
|
||||
return "VALUES " + ", ".join([items_sql] * num_values)
|
||||
|
||||
if django.VERSION < (1, 8):
|
||||
def year_lookup_bounds(self, value):
|
||||
# Again, no microseconds
|
||||
first = '{0}-01-01 00:00:00'
|
||||
second = '{0}-12-31 23:59:59.999999'
|
||||
return [first.format(value), second.format(value)]
|
||||
|
||||
def year_lookup_bounds_for_datetime_field(self, value):
|
||||
# Django 1.6
|
||||
# Again, no microseconds
|
||||
first, second = super(DatabaseOperations,
|
||||
self).year_lookup_bounds_for_datetime_field(value)
|
||||
if self.connection.mysql_version >= (5, 6, 4):
|
||||
return [first.replace(microsecond=0), second]
|
||||
else:
|
||||
return [first.replace(microsecond=0),
|
||||
second.replace(microsecond=0)]
|
||||
|
||||
def sequence_reset_by_name_sql(self, style, sequences):
|
||||
# Truncate already resets the AUTO_INCREMENT field from
|
||||
# MySQL version 5.0.13 onwards. Refs #16961.
|
||||
res = []
|
||||
if self.connection.mysql_version < (5, 0, 13):
|
||||
fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format(
|
||||
alter=style.SQL_KEYWORD('ALTER'),
|
||||
table=style.SQL_KEYWORD('TABLE'),
|
||||
auto_inc=style.SQL_KEYWORD('AUTO_INCREMENT'),
|
||||
field=style.SQL_FIELD('= 1')
|
||||
)
|
||||
for sequence in sequences:
|
||||
tablename = style.SQL_TABLE(self.quote_name(sequence['table']))
|
||||
res.append(fmt.format(tablename=tablename))
|
||||
return res
|
||||
return res
|
||||
|
||||
def savepoint_create_sql(self, sid):
|
||||
return "SAVEPOINT {0}".format(sid)
|
||||
|
||||
def savepoint_commit_sql(self, sid):
|
||||
return "RELEASE SAVEPOINT {0}".format(sid)
|
||||
|
||||
def savepoint_rollback_sql(self, sid):
|
||||
return "ROLLBACK TO SAVEPOINT {0}".format(sid)
|
||||
|
||||
def combine_expression(self, connector, sub_expressions):
|
||||
"""
|
||||
MySQL requires special cases for ^ operators in query expressions
|
||||
"""
|
||||
if connector == '^':
|
||||
return 'POW(%s)' % ','.join(sub_expressions)
|
||||
return super(DatabaseOperations, self).combine_expression(
|
||||
connector, sub_expressions)
|
||||
|
||||
def get_db_converters(self, expression):
|
||||
# New in Django 1.8
|
||||
converters = super(DatabaseOperations, self).get_db_converters(
|
||||
expression)
|
||||
internal_type = expression.output_field.get_internal_type()
|
||||
if internal_type in ['BooleanField', 'NullBooleanField']:
|
||||
converters.append(self.convert_booleanfield_value)
|
||||
if internal_type == 'UUIDField':
|
||||
converters.append(self.convert_uuidfield_value)
|
||||
if internal_type == 'TextField':
|
||||
converters.append(self.convert_textfield_value)
|
||||
return converters
|
||||
|
||||
def convert_booleanfield_value(self, value,
|
||||
expression, connection, context):
|
||||
# New in Django 1.8
|
||||
if value in (0, 1):
|
||||
value = bool(value)
|
||||
return value
|
||||
|
||||
def convert_uuidfield_value(self, value, expression, connection, context):
|
||||
# New in Django 1.8
|
||||
if value is not None:
|
||||
value = uuid.UUID(value)
|
||||
return value
|
||||
|
||||
def convert_textfield_value(self, value, expression, connection, context):
|
||||
# New in Django 1.8
|
||||
if value is not None:
|
||||
value = force_text(value)
|
||||
return value
|
|
@ -0,0 +1,86 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
# New file added for Django 1.7
|
||||
|
||||
import django
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
else:
|
||||
from django.db.backends.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.models import NOT_PROVIDED
|
||||
|
||||
|
||||
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||
|
||||
sql_rename_table = "RENAME TABLE %(old_table)s TO %(new_table)s"
|
||||
|
||||
sql_alter_column_null = "MODIFY %(column)s %(type)s NULL"
|
||||
sql_alter_column_not_null = "MODIFY %(column)s %(type)s NOT NULL"
|
||||
sql_alter_column_type = "MODIFY %(column)s %(type)s"
|
||||
sql_rename_column = "ALTER TABLE %(table)s CHANGE %(old_column)s " \
|
||||
"%(new_column)s %(type)s"
|
||||
|
||||
sql_delete_unique = "ALTER TABLE %(table)s DROP INDEX %(name)s"
|
||||
|
||||
sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s FOREIGN " \
|
||||
"KEY (%(column)s) REFERENCES %(to_table)s (%(to_column)s)"
|
||||
sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s"
|
||||
|
||||
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
|
||||
|
||||
alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;'
|
||||
alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;'
|
||||
|
||||
sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \
|
||||
"PRIMARY KEY (%(columns)s)"
|
||||
sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY"
|
||||
|
||||
def quote_value(self, value):
|
||||
# Inner import to allow module to fail to load gracefully
|
||||
from mysql.connector.conversion import MySQLConverter
|
||||
return MySQLConverter.quote(MySQLConverter.escape(value))
|
||||
|
||||
def skip_default(self, field):
|
||||
"""
|
||||
MySQL doesn't accept default values for longtext and longblob
|
||||
and implicitly treats these columns as nullable.
|
||||
"""
|
||||
return field.db_type(self.connection) in ('longtext', 'longblob')
|
||||
|
||||
def add_field(self, model, field):
|
||||
super(DatabaseSchemaEditor, self).add_field(model, field)
|
||||
|
||||
# Simulate the effect of a one-off default.
|
||||
if (self.skip_default(field)
|
||||
and field.default not in (None, NOT_PROVIDED)):
|
||||
effective_default = self.effective_default(field)
|
||||
self.execute('UPDATE %(table)s SET %(column)s = %%s' % {
|
||||
'table': self.quote_name(model._meta.db_table),
|
||||
'column': self.quote_name(field.column),
|
||||
}, [effective_default])
|
||||
|
||||
def _model_indexes_sql(self, model):
|
||||
# New in Django 1.8
|
||||
storage = self.connection.introspection.get_storage_engine(
|
||||
self.connection.cursor(), model._meta.db_table
|
||||
)
|
||||
if storage == "InnoDB":
|
||||
for field in model._meta.local_fields:
|
||||
if (field.db_index and not field.unique
|
||||
and field.get_internal_type() == "ForeignKey"):
|
||||
# Temporary setting db_index to False (in memory) to
|
||||
# disable index creation for FKs (index automatically
|
||||
# created by MySQL)
|
||||
field.db_index = False
|
||||
return super(DatabaseSchemaEditor, self)._model_indexes_sql(model)
|
||||
|
||||
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
|
||||
# New in Django 1.8
|
||||
# Keep null property of old field, if it has changed, it will be
|
||||
# handled separately
|
||||
if old_field.null:
|
||||
new_type += " NULL"
|
||||
else:
|
||||
new_type += " NOT NULL"
|
||||
return super(DatabaseSchemaEditor, self)._alter_column_type_sql(
|
||||
table, old_field, new_field, new_type)
|
|
@ -0,0 +1,65 @@
|
|||
# MySQL Connector/Python - MySQL driver written in Python.
|
||||
|
||||
import django
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.backends.base.validation import BaseDatabaseValidation
|
||||
else:
|
||||
from django.db.backends import BaseDatabaseValidation
|
||||
|
||||
if django.VERSION < (1, 7):
|
||||
from django.db import models
|
||||
else:
|
||||
from django.core import checks
|
||||
from django.db import connection
|
||||
|
||||
|
||||
class DatabaseValidation(BaseDatabaseValidation):
|
||||
if django.VERSION < (1, 7):
|
||||
def validate_field(self, errors, opts, f):
|
||||
"""
|
||||
MySQL has the following field length restriction:
|
||||
No character (varchar) fields can have a length exceeding 255
|
||||
characters if they have a unique index on them.
|
||||
"""
|
||||
varchar_fields = (models.CharField,
|
||||
models.CommaSeparatedIntegerField,
|
||||
models.SlugField)
|
||||
if isinstance(f, varchar_fields) and f.max_length > 255 and f.unique:
|
||||
msg = ('"%(name)s": %(cls)s cannot have a "max_length" greater '
|
||||
'than 255 when using "unique=True".')
|
||||
errors.add(opts, msg % {'name': f.name,
|
||||
'cls': f.__class__.__name__})
|
||||
|
||||
else:
|
||||
def check_field(self, field, **kwargs):
|
||||
"""
|
||||
MySQL has the following field length restriction:
|
||||
No character (varchar) fields can have a length exceeding 255
|
||||
characters if they have a unique index on them.
|
||||
"""
|
||||
# Django 1.7
|
||||
errors = super(DatabaseValidation, self).check_field(field,
|
||||
**kwargs)
|
||||
|
||||
# Ignore any related fields.
|
||||
if getattr(field, 'rel', None) is None:
|
||||
field_type = field.db_type(connection)
|
||||
|
||||
if field_type is None:
|
||||
return errors
|
||||
|
||||
if (field_type.startswith('varchar') # Look for CharFields...
|
||||
and field.unique # ... that are unique
|
||||
and (field.max_length is None or
|
||||
int(field.max_length) > 255)):
|
||||
errors.append(
|
||||
checks.Error(
|
||||
('MySQL does not allow unique CharFields to have a '
|
||||
'max_length > 255.'),
|
||||
hint=None,
|
||||
obj=field,
|
||||
id='mysql.E001',
|
||||
)
|
||||
)
|
||||
return errors
|
Loading…
Add table
Add a link
Reference in a new issue