Removed the mysql_old backend. It smells bad and has no friends.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7949 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-07-18 17:38:53 +00:00
parent d261c1d000
commit c681f4070c
10 changed files with 8 additions and 362 deletions

View File

@ -1,215 +0,0 @@
"""
MySQL database backend for Django.
Requires MySQLdb: http://sourceforge.net/projects/mysql-python
"""
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
from django.utils.encoding import force_unicode
try:
import MySQLdb as Database
except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE
import types
import re
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
django_conversions = conversions.copy()
django_conversions.update({
types.BooleanType: util.rev_typecast_boolean,
FIELD_TYPE.DATETIME: util.typecast_timestamp,
FIELD_TYPE.DATE: util.typecast_date,
FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.STRING: force_unicode,
FIELD_TYPE.VAR_STRING: force_unicode,
# Note: We don't add a convertor for BLOB here. Doesn't seem to be required.
})
# This should match the numerical portion of the version numbers (we can treat
# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
# This is an extra debug layer over MySQL queries, to display warnings.
# It's only used when DEBUG=True.
class MysqlDebugWrapper:
def __init__(self, cursor):
self.cursor = cursor
def execute(self, sql, params=()):
try:
return self.cursor.execute(sql, params)
except Database.Warning, w:
self.cursor.execute("SHOW WARNINGS")
raise Database.Warning("%s: %s" % (w, self.cursor.fetchall()))
def executemany(self, sql, param_list):
try:
return self.cursor.executemany(sql, param_list)
except Database.Warning, w:
self.cursor.execute("SHOW WARNINGS")
raise Database.Warning("%s: %s" % (w, self.cursor.fetchall()))
def __getattr__(self, attr):
if attr in self.__dict__:
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
class DatabaseFeatures(BaseDatabaseFeatures):
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
supports_usecs = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
def date_trunc_sql(self, lookup_type, field_name):
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')
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(%s, '%s') AS DATETIME)" % (field_name, format_str)
return sql
def drop_foreignkey_sql(self):
return "DROP FOREIGN KEY"
def fulltext_search_sql(self, field_name):
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
def no_limit_value(self):
# 2**64 - 1, as recommended by the MySQL documentation
return 18446744073709551615L
def quote_name(self, name):
if name.startswith("`") and name.endswith("`"):
return name # Quoting once is enough.
return "`%s`" % name
def random_function_sql(self):
return 'RAND()'
def sql_flush(self, style, tables, sequences):
# NB: The generated SQL below is specific to MySQL
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
# to clear all tables of all data
if tables:
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
for table in tables:
sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
# 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
# to reset sequence indices
sql.extend(["%s %s %s %s %s;" % \
(style.SQL_KEYWORD('ALTER'),
style.SQL_KEYWORD('TABLE'),
style.SQL_TABLE(self.quote_name(sequence['table'])),
style.SQL_KEYWORD('AUTO_INCREMENT'),
style.SQL_FIELD('= 1'),
) for sequence in sequences])
return sql
else:
return []
class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
operators = {
'exact': '= BINARY %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',
}
def __init__(self, **kwargs):
super(DatabaseWrapper, self).__init__(**kwargs)
self.server_version = None
def _valid_connection(self):
if self.connection is not None:
try:
self.connection.ping()
return True
except DatabaseError:
self.connection.close()
self.connection = None
return False
def _cursor(self, settings):
if not self._valid_connection():
kwargs = {
# Note: use_unicode intentonally not set to work around some
# backwards-compat issues. We do it manually.
'user': settings.DATABASE_USER,
'db': settings.DATABASE_NAME,
'passwd': settings.DATABASE_PASSWORD,
'conv': django_conversions,
}
if settings.DATABASE_HOST.startswith('/'):
kwargs['unix_socket'] = settings.DATABASE_HOST
else:
kwargs['host'] = settings.DATABASE_HOST
if settings.DATABASE_PORT:
kwargs['port'] = int(settings.DATABASE_PORT)
kwargs.update(self.options)
self.connection = Database.connect(**kwargs)
cursor = self.connection.cursor()
if self.connection.get_server_info() >= '4.1' and not self.connection.character_set_name().startswith('utf8'):
if hasattr(self.connection, 'charset'):
# MySQLdb < 1.2.1 backwards-compat hacks.
conn = self.connection
cursor.execute("SET NAMES 'utf8'")
cursor.execute("SET CHARACTER SET 'utf8'")
to_str = lambda u, dummy=None, c=conn: c.literal(u.encode('utf-8'))
conn.converter[unicode] = to_str
else:
self.connection.set_character_set('utf8')
else:
cursor = self.connection.cursor()
return cursor
def make_debug_cursor(self, cursor):
return BaseDatabaseWrapper.make_debug_cursor(self, MysqlDebugWrapper(cursor))
def _rollback(self):
try:
BaseDatabaseWrapper._rollback(self)
except Database.NotSupportedError:
pass
def get_server_version(self):
if not self.server_version:
if not self._valid_connection():
self.cursor()
m = server_version_re.match(self.connection.get_server_info())
if not m:
raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
self.server_version = tuple([int(x) for x in m.groups()])
return self.server_version

View File

@ -1,14 +0,0 @@
from django.conf import settings
import os
def runshell():
args = ['']
args += ["--user=%s" % settings.DATABASE_USER]
if settings.DATABASE_PASSWORD:
args += ["--password=%s" % settings.DATABASE_PASSWORD]
if settings.DATABASE_HOST:
args += ["--host=%s" % settings.DATABASE_HOST]
if settings.DATABASE_PORT:
args += ["--port=%s" % settings.DATABASE_PORT]
args += [settings.DATABASE_NAME]
os.execvp('mysql', args)

View File

@ -1,28 +0,0 @@
# 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.
DATA_TYPES = {
'AutoField': 'integer AUTO_INCREMENT',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'IPAddressField': 'char(15)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PhoneNumberField': 'varchar(20)',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'USStateField': 'varchar(2)',
}

View File

@ -1,96 +0,0 @@
from django.db.backends.mysql_old.base import DatabaseOperations
from MySQLdb import ProgrammingError, OperationalError
from MySQLdb.constants import FIELD_TYPE
import re
quote_name = DatabaseOperations().quote_name
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
def get_table_list(cursor):
"Returns a list of table names in the current database."
cursor.execute("SHOW TABLES")
return [row[0] for row in cursor.fetchall()]
def get_table_description(cursor, table_name):
"Returns a description of the table, with the DB-API cursor.description interface."
cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
return cursor.description
def _name_to_index(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(get_table_description(cursor, table_name))])
def get_relations(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.
"""
my_field_dict = _name_to_index(cursor, table_name)
constraints = []
relations = {}
try:
# This should work for MySQL 5.0.
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])
constraints.extend(cursor.fetchall())
except (ProgrammingError, OperationalError):
# Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
# Go through all constraints and save the equal matches.
cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
for row in cursor.fetchall():
pos = 0
while True:
match = foreign_key_re.search(row[1], pos)
if match == None:
break
pos = match.end()
constraints.append(match.groups())
for my_fieldname, other_table, other_field in constraints:
other_field_index = _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_indexes(cursor, table_name):
"""
Returns a dictionary of fieldname -> infodict for the given table,
where each infodict is in the format:
{'primary_key': boolean representing whether it's the primary key,
'unique': boolean representing whether it's a unique index}
"""
cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
indexes = {}
for row in cursor.fetchall():
indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
return indexes
DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField',
FIELD_TYPE.DECIMAL: 'DecimalField',
FIELD_TYPE.DATE: 'DateField',
FIELD_TYPE.DATETIME: 'DateTimeField',
FIELD_TYPE.DOUBLE: 'FloatField',
FIELD_TYPE.FLOAT: 'FloatField',
FIELD_TYPE.INT24: 'IntegerField',
FIELD_TYPE.LONG: 'IntegerField',
FIELD_TYPE.LONGLONG: 'IntegerField',
FIELD_TYPE.SHORT: 'IntegerField',
FIELD_TYPE.STRING: 'TextField',
FIELD_TYPE.TIMESTAMP: 'DateTimeField',
FIELD_TYPE.TINY: 'IntegerField',
FIELD_TYPE.TINY_BLOB: 'TextField',
FIELD_TYPE.MEDIUM_BLOB: 'TextField',
FIELD_TYPE.LONG_BLOB: 'TextField',
FIELD_TYPE.VAR_STRING: 'CharField',
}

View File

@ -140,7 +140,6 @@ def create_test_db(verbosity=1, autoclobber=False):
'postgresql': get_postgresql_create_suffix, 'postgresql': get_postgresql_create_suffix,
'postgresql_psycopg2': get_postgresql_create_suffix, 'postgresql_psycopg2': get_postgresql_create_suffix,
'mysql': get_mysql_create_suffix, 'mysql': get_mysql_create_suffix,
'mysql_old': get_mysql_create_suffix,
}.get(settings.DATABASE_ENGINE, lambda: '')() }.get(settings.DATABASE_ENGINE, lambda: '')()
if settings.TEST_DATABASE_NAME: if settings.TEST_DATABASE_NAME:
TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME

View File

@ -278,8 +278,8 @@ DATABASE_ENGINE
Default: ``''`` (Empty string) Default: ``''`` (Empty string)
The database backend to use. The build-in database backends are The database backend to use. The build-in database backends are
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'sqlite3'``, and
``'sqlite3'``, and ``'oracle'``. ``'oracle'``.
In the Django development version, you can use a database backend that doesn't In the Django development version, you can use a database backend that doesn't
ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e. ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
@ -1029,7 +1029,7 @@ The character set encoding used to create the test database. The value of this
string is passed directly through to the database, so its format is string is passed directly through to the database, so its format is
backend-specific. backend-specific.
Supported for the PostgreSQL_ (``postgresql``, ``postgresql_psycopg2``) and MySQL_ (``mysql``, ``mysql_old``) backends. Supported for the PostgreSQL_ (``postgresql``, ``postgresql_psycopg2``) and MySQL_ (``mysql``) backends.
.. _PostgreSQL: http://www.postgresql.org/docs/8.2/static/multibyte.html .. _PostgreSQL: http://www.postgresql.org/docs/8.2/static/multibyte.html
.. _MySQL: http://www.mysql.org/doc/refman/5.0/en/charset-database.html .. _MySQL: http://www.mysql.org/doc/refman/5.0/en/charset-database.html
@ -1044,8 +1044,8 @@ Default: ``None``
The collation order to use when creating the test database. This value is The collation order to use when creating the test database. This value is
passed directly to the backend, so its format is backend-specific. passed directly to the backend, so its format is backend-specific.
Only supported for ``mysql`` and ``mysql_old`` backends (see `section 10.3.2`_ Only supported for the ``mysql`` backend (see `section 10.3.2`_ of the MySQL
of the MySQL manual for details). manual for details).
.. _section 10.3.2: http://www.mysql.org/doc/refman/5.0/en/charset-database.html .. _section 10.3.2: http://www.mysql.org/doc/refman/5.0/en/charset-database.html

View File

@ -58,7 +58,7 @@ __test__ = {'API_TESTS': """
# Database flushing does not work on MySQL with the default storage engine # Database flushing does not work on MySQL with the default storage engine
# because it requires transaction support. # because it requires transaction support.
if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'): if settings.DATABASE_ENGINE != 'mysql':
__test__['API_TESTS'] += \ __test__['API_TESTS'] += \
""" """
# Reset the database representation of this app. This will delete all data. # Reset the database representation of this app. This will delete all data.

View File

@ -380,7 +380,7 @@ FieldError: Join on field 'headline' not permitted.
"""} """}
if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'): if settings.DATABASE_ENGINE != 'mysql':
__test__['API_TESTS'] += r""" __test__['API_TESTS'] += r"""
# grouping and backreferences # grouping and backreferences
>>> Article.objects.filter(headline__regex=r'b(.).*b\1') >>> Article.objects.filter(headline__regex=r'b(.).*b\1')

View File

@ -25,7 +25,7 @@ from django.conf import settings
building_docs = getattr(settings, 'BUILDING_DOCS', False) building_docs = getattr(settings, 'BUILDING_DOCS', False)
if building_docs or settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'): if building_docs or settings.DATABASE_ENGINE != 'mysql':
__test__['API_TESTS'] += """ __test__['API_TESTS'] += """
# the default behavior is to autocommit after each save() action # the default behavior is to autocommit after each save() action
>>> def create_a_reporter_then_fail(first, last): >>> def create_a_reporter_then_fail(first, last):