Initial upload
This commit is contained in:
commit
c59faac144
|
@ -0,0 +1,53 @@
|
|||
|
||||
Experimental Django backend using MySQL Connector/Python
|
||||
==============================================================================
|
||||
|
||||
Disclaimer
|
||||
=====================
|
||||
|
||||
!!!!!!!!!!! THIS IS STILL IN DEVELOPMENT !!!!!!!!!!!!!!!!!!!
|
||||
!!!!!!!! EXPECT THING TO NOT WORK OR GO WRONG !!!!!!!!!!!!!!
|
||||
!!!! DO NOT USE IN PRODUCTION etc.. etc... !!!!!!!!!!!!!!!!!
|
||||
|
||||
Ah, and make backups! :-)
|
||||
|
||||
Dependencies
|
||||
=====================
|
||||
|
||||
* Python 2.3 or greater.
|
||||
* Django 1.2: http://www.djangoproject.com
|
||||
* MySQL Connector/Python (currently in development)
|
||||
shell> bzr checkout lp:~mysql/myconnpy/main myconnpy
|
||||
shell> cd myconnpy
|
||||
shell> python setup.py install
|
||||
|
||||
Installation
|
||||
=====================
|
||||
|
||||
To install the Django backend, do the following:
|
||||
shell> python ./setup.py install
|
||||
|
||||
It will install it in site-packages/mysql/django
|
||||
|
||||
Usage
|
||||
=====================
|
||||
|
||||
To configure your Django project to use the backend, set the engine in
|
||||
your settings.py like this:
|
||||
|
||||
DATABASE_ENGINE='mysql.django'
|
||||
|
||||
The above assumes you installed the mysql.django module somewhere where
|
||||
Python can find it (see Installation).
|
||||
|
||||
Some caveats though:
|
||||
* When you were using the DATABASE_OPTIONS and stored settings in an
|
||||
option file, that will not work anymore. Do it the normal Django way.
|
||||
* You can't use UNIX Sockets (yet).
|
||||
|
||||
Report problems
|
||||
=====================
|
||||
|
||||
Report problems to Geert Vanderkelen <geert.vanderkelen@sun.com>
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Connector/Python, native MySQL driver written in Python.
|
||||
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
# This file should be automatically generated on each release
|
||||
version = (0, 0, 1, 'alpha', '')
|
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
MySQL database backend for Django using MySQL Connector/Python.
|
||||
|
||||
"""
|
||||
|
||||
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||
try:
|
||||
import mysql.connector as Database
|
||||
except ImportError, e:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
|
||||
|
||||
# We want version (1, 2, 1, 'final', 2) or later. We can't just use
|
||||
# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
|
||||
# inadvertently passes the version test.
|
||||
version = Database.__version__
|
||||
if ( version[:3] < (0, 0, 2) ):
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured("MySQL Connector/Python 0.0.2 or newer is required; you have %s" % Database.__version__)
|
||||
|
||||
import mysql.connector.conversion
|
||||
import re
|
||||
|
||||
from django.db.backends import *
|
||||
from django.db.backends.mysql.client import DatabaseClient
|
||||
from django.db.backends.mysql.creation import DatabaseCreation
|
||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||
from django.db.backends.mysql.validation import DatabaseValidation
|
||||
from django.utils.safestring import SafeString, SafeUnicode
|
||||
|
||||
# Raise exceptions for database warnings if DEBUG is on
|
||||
from django.conf import settings
|
||||
if settings.DEBUG:
|
||||
from warnings import filterwarnings
|
||||
filterwarnings("error", category=Database.Warning)
|
||||
|
||||
DatabaseError = Database.DatabaseError
|
||||
IntegrityError = Database.IntegrityError
|
||||
|
||||
|
||||
class DjangoMySQLConverter(Database.conversion.MySQLConverter):
|
||||
pass
|
||||
"""
|
||||
def _TIME_to_python(self, v, dsc=None):
|
||||
return util.typecast_time(v)
|
||||
|
||||
def _decimal(self, v, desc=None):
|
||||
return util.typecast_decimal(v)
|
||||
"""
|
||||
# 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})')
|
||||
|
||||
# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
|
||||
# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
|
||||
# point is to raise Warnings as exceptions, this can be done with the Python
|
||||
# warning module, and this is setup when the connection is created, and the
|
||||
# standard util.CursorDebugWrapper can be used. Also, using sql_mode
|
||||
# TRADITIONAL will automatically cause most warnings to be treated as errors.
|
||||
|
||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
autoindexes_primary_keys = False
|
||||
inline_fk_references = 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 limit_offset_sql(self, limit, offset=None):
|
||||
# 'LIMIT 20,40'
|
||||
sql = "LIMIT "
|
||||
if offset and offset != 0:
|
||||
sql += "%s," % offset
|
||||
return sql + str(limit)
|
||||
|
||||
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 []
|
||||
|
||||
def value_to_db_datetime(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# MySQL doesn't support tz-aware datetimes
|
||||
if value.tzinfo is not None:
|
||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||
|
||||
# MySQL doesn't support microseconds
|
||||
return unicode(value.replace(microsecond=0))
|
||||
|
||||
def value_to_db_time(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# MySQL doesn't support tz-aware datetimes
|
||||
if value.tzinfo is not None:
|
||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||
|
||||
# MySQL doesn't support microseconds
|
||||
return unicode(value.replace(microsecond=0))
|
||||
|
||||
def year_lookup_bounds(self, value):
|
||||
# Again, no microseconds
|
||||
first = '%s-01-01 00:00:00'
|
||||
second = '%s-12-31 23:59:59.99'
|
||||
return [first % value, second % value]
|
||||
|
||||
|
||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(**kwargs)
|
||||
self.server_version = None
|
||||
|
||||
self.features = DatabaseFeatures()
|
||||
self.ops = DatabaseOperations()
|
||||
self.client = DatabaseClient()
|
||||
self.creation = DatabaseCreation(self)
|
||||
self.introspection = DatabaseIntrospection(self)
|
||||
self.validation = DatabaseValidation()
|
||||
|
||||
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 = {
|
||||
#'conv': django_conversions,
|
||||
'charset': 'utf8',
|
||||
'use_unicode': True,
|
||||
}
|
||||
if settings.DATABASE_USER:
|
||||
kwargs['user'] = settings.DATABASE_USER
|
||||
if settings.DATABASE_NAME:
|
||||
kwargs['db'] = settings.DATABASE_NAME
|
||||
if settings.DATABASE_PASSWORD:
|
||||
kwargs['passwd'] = settings.DATABASE_PASSWORD
|
||||
if settings.DATABASE_HOST.startswith('/'):
|
||||
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||
elif settings.DATABASE_HOST:
|
||||
kwargs['host'] = settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||
kwargs.update(self.options)
|
||||
self.connection = Database.connect(**kwargs)
|
||||
self.connection.set_converter_class(DjangoMySQLConverter)
|
||||
cursor = self.connection.cursor()
|
||||
return 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()
|
||||
self.server_version = self.connection.get_server_version()
|
||||
#m = server_version_re.match(self.connection.get_server_version())
|
||||
#if not m:
|
||||
# raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_version())
|
||||
#self.server_version = tuple([int(x) for x in m.groups()])
|
||||
return self.server_version
|
|
@ -0,0 +1,27 @@
|
|||
from django.conf import settings
|
||||
import os
|
||||
|
||||
def runshell():
|
||||
args = ['']
|
||||
db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
|
||||
user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
|
||||
passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
|
||||
host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
|
||||
port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
|
||||
defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
|
||||
# Seems to be no good way to set sql_mode with CLI
|
||||
|
||||
if defaults_file:
|
||||
args += ["--defaults-file=%s" % defaults_file]
|
||||
if user:
|
||||
args += ["--user=%s" % user]
|
||||
if passwd:
|
||||
args += ["--password=%s" % passwd]
|
||||
if host:
|
||||
args += ["--host=%s" % host]
|
||||
if port:
|
||||
args += ["--port=%s" % port]
|
||||
if db:
|
||||
args += [db]
|
||||
|
||||
os.execvp('mysql', args)
|
|
@ -0,0 +1,29 @@
|
|||
# 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',
|
||||
'ImageField': 'varchar(%(max_length)s)',
|
||||
'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)',
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
from base import DatabaseOperations
|
||||
from mysql.connector.errors import ProgrammingError, OperationalError
|
||||
from mysql.connector.constants import FieldType
|
||||
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 = {
|
||||
FieldType.BLOB: 'TextField',
|
||||
FieldType.STRING: 'CharField',
|
||||
FieldType.DECIMAL: 'DecimalField',
|
||||
FieldType.DATE: 'DateField',
|
||||
FieldType.DATETIME: 'DateTimeField',
|
||||
FieldType.DOUBLE: 'FloatField',
|
||||
FieldType.FLOAT: 'FloatField',
|
||||
FieldType.INT24: 'IntegerField',
|
||||
FieldType.LONG: 'IntegerField',
|
||||
FieldType.LONGLONG: 'IntegerField',
|
||||
FieldType.SHORT: 'IntegerField',
|
||||
FieldType.STRING: 'CharField',
|
||||
FieldType.TIMESTAMP: 'DateTimeField',
|
||||
FieldType.TINY: 'IntegerField',
|
||||
FieldType.TINY_BLOB: 'TextField',
|
||||
FieldType.MEDIUM_BLOB: 'TextField',
|
||||
FieldType.LONG_BLOB: 'TextField',
|
||||
FieldType.VAR_STRING: 'CharField',
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Connector/Python, native MySQL driver written in Python.
|
||||
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
# This file should be automatically generated on each release
|
||||
version = (0, 0, 1, 'alpha', '')
|
Binary file not shown.
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
MySQL database backend for Django using MySQL Connector/Python.
|
||||
|
||||
"""
|
||||
|
||||
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||
try:
|
||||
import mysql.connector as Database
|
||||
except ImportError, e:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
|
||||
|
||||
# We want version (1, 2, 1, 'final', 2) or later. We can't just use
|
||||
# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
|
||||
# inadvertently passes the version test.
|
||||
version = Database.__version__
|
||||
if ( version[:3] < (0, 0, 2) ):
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured("MySQL Connector/Python 0.0.2 or newer is required; you have %s" % Database.__version__)
|
||||
|
||||
import mysql.connector.conversion
|
||||
import re
|
||||
|
||||
from django.db.backends import *
|
||||
from django.db.backends.mysql.client import DatabaseClient
|
||||
from django.db.backends.mysql.creation import DatabaseCreation
|
||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||
from django.db.backends.mysql.validation import DatabaseValidation
|
||||
from django.utils.safestring import SafeString, SafeUnicode
|
||||
|
||||
# Raise exceptions for database warnings if DEBUG is on
|
||||
from django.conf import settings
|
||||
if settings.DEBUG:
|
||||
from warnings import filterwarnings
|
||||
filterwarnings("error", category=Database.Warning)
|
||||
|
||||
DatabaseError = Database.DatabaseError
|
||||
IntegrityError = Database.IntegrityError
|
||||
|
||||
|
||||
class DjangoMySQLConverter(Database.conversion.MySQLConverter):
|
||||
pass
|
||||
"""
|
||||
def _TIME_to_python(self, v, dsc=None):
|
||||
return util.typecast_time(v)
|
||||
|
||||
def _decimal(self, v, desc=None):
|
||||
return util.typecast_decimal(v)
|
||||
"""
|
||||
# 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})')
|
||||
|
||||
# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
|
||||
# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
|
||||
# point is to raise Warnings as exceptions, this can be done with the Python
|
||||
# warning module, and this is setup when the connection is created, and the
|
||||
# standard util.CursorDebugWrapper can be used. Also, using sql_mode
|
||||
# TRADITIONAL will automatically cause most warnings to be treated as errors.
|
||||
|
||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
autoindexes_primary_keys = False
|
||||
inline_fk_references = 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 limit_offset_sql(self, limit, offset=None):
|
||||
# 'LIMIT 20,40'
|
||||
sql = "LIMIT "
|
||||
if offset and offset != 0:
|
||||
sql += "%s," % offset
|
||||
return sql + str(limit)
|
||||
|
||||
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 []
|
||||
|
||||
def value_to_db_datetime(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# MySQL doesn't support tz-aware datetimes
|
||||
if value.tzinfo is not None:
|
||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||
|
||||
# MySQL doesn't support microseconds
|
||||
return unicode(value.replace(microsecond=0))
|
||||
|
||||
def value_to_db_time(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# MySQL doesn't support tz-aware datetimes
|
||||
if value.tzinfo is not None:
|
||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||
|
||||
# MySQL doesn't support microseconds
|
||||
return unicode(value.replace(microsecond=0))
|
||||
|
||||
def year_lookup_bounds(self, value):
|
||||
# Again, no microseconds
|
||||
first = '%s-01-01 00:00:00'
|
||||
second = '%s-12-31 23:59:59.99'
|
||||
return [first % value, second % value]
|
||||
|
||||
|
||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(**kwargs)
|
||||
self.server_version = None
|
||||
|
||||
self.features = DatabaseFeatures()
|
||||
self.ops = DatabaseOperations()
|
||||
self.client = DatabaseClient()
|
||||
self.creation = DatabaseCreation(self)
|
||||
self.introspection = DatabaseIntrospection(self)
|
||||
self.validation = DatabaseValidation()
|
||||
|
||||
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 = {
|
||||
#'conv': django_conversions,
|
||||
'charset': 'utf8',
|
||||
'use_unicode': True,
|
||||
}
|
||||
if settings.DATABASE_USER:
|
||||
kwargs['user'] = settings.DATABASE_USER
|
||||
if settings.DATABASE_NAME:
|
||||
kwargs['db'] = settings.DATABASE_NAME
|
||||
if settings.DATABASE_PASSWORD:
|
||||
kwargs['passwd'] = settings.DATABASE_PASSWORD
|
||||
if settings.DATABASE_HOST.startswith('/'):
|
||||
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||
elif settings.DATABASE_HOST:
|
||||
kwargs['host'] = settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||
kwargs.update(self.options)
|
||||
self.connection = Database.connect(**kwargs)
|
||||
self.connection.set_converter_class(DjangoMySQLConverter)
|
||||
cursor = self.connection.cursor()
|
||||
return 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()
|
||||
self.server_version = self.connection.get_server_version()
|
||||
#m = server_version_re.match(self.connection.get_server_version())
|
||||
#if not m:
|
||||
# raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_version())
|
||||
#self.server_version = tuple([int(x) for x in m.groups()])
|
||||
return self.server_version
|
|
@ -0,0 +1,27 @@
|
|||
from django.conf import settings
|
||||
import os
|
||||
|
||||
def runshell():
|
||||
args = ['']
|
||||
db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
|
||||
user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
|
||||
passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
|
||||
host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
|
||||
port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
|
||||
defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
|
||||
# Seems to be no good way to set sql_mode with CLI
|
||||
|
||||
if defaults_file:
|
||||
args += ["--defaults-file=%s" % defaults_file]
|
||||
if user:
|
||||
args += ["--user=%s" % user]
|
||||
if passwd:
|
||||
args += ["--password=%s" % passwd]
|
||||
if host:
|
||||
args += ["--host=%s" % host]
|
||||
if port:
|
||||
args += ["--port=%s" % port]
|
||||
if db:
|
||||
args += [db]
|
||||
|
||||
os.execvp('mysql', args)
|
|
@ -0,0 +1,29 @@
|
|||
# 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',
|
||||
'ImageField': 'varchar(%(max_length)s)',
|
||||
'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)',
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
from base import DatabaseOperations
|
||||
from mysql.connector.errors import ProgrammingError, OperationalError
|
||||
from mysql.connector.constants import FieldType
|
||||
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 = {
|
||||
FieldType.BLOB: 'TextField',
|
||||
FieldType.STRING: 'CharField',
|
||||
FieldType.DECIMAL: 'DecimalField',
|
||||
FieldType.DATE: 'DateField',
|
||||
FieldType.DATETIME: 'DateTimeField',
|
||||
FieldType.DOUBLE: 'FloatField',
|
||||
FieldType.FLOAT: 'FloatField',
|
||||
FieldType.INT24: 'IntegerField',
|
||||
FieldType.LONG: 'IntegerField',
|
||||
FieldType.LONGLONG: 'IntegerField',
|
||||
FieldType.SHORT: 'IntegerField',
|
||||
FieldType.STRING: 'CharField',
|
||||
FieldType.TIMESTAMP: 'DateTimeField',
|
||||
FieldType.TINY: 'IntegerField',
|
||||
FieldType.TINY_BLOB: 'TextField',
|
||||
FieldType.MEDIUM_BLOB: 'TextField',
|
||||
FieldType.LONG_BLOB: 'TextField',
|
||||
FieldType.VAR_STRING: 'CharField',
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Connector/Python, native MySQL driver written in Python.
|
||||
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
from mysql.django._version import version as mysql_django_version
|
||||
|
||||
_name = 'Django Database Backend using MySQL Connector/Python'
|
||||
_version = '%d.%d.%d' % mysql_django_version[0:3]
|
||||
_packages = ['mysql','mysql.django']
|
||||
|
||||
setup(
|
||||
name = _name,
|
||||
version = _version,
|
||||
author = 'Geert Vanderkelen',
|
||||
author_email = 'geert.vanderkelen@sun.com',
|
||||
url = 'http://dev.mysql.com/usingmysql/python/',
|
||||
download_url = 'http://dev.mysql.com/downloads/connector/python/',
|
||||
packages = _packages
|
||||
)
|
Loading…
Reference in New Issue