From: <fwi...@us...> - 2008-01-09 23:30:49
|
Revision: 4012 http://jython.svn.sourceforge.net/jython/?rev=4012&view=rev Author: fwierzbicki Date: 2008-01-09 15:30:44 -0800 (Wed, 09 Jan 2008) Log Message: ----------- initial commit of a mysql backend for jython/django. I haven't looked at it in a while so who knows how well it will work. Added Paths: ----------- trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/ trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/__init__.py trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/base.py trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/client.py trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/creation.py trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/introspection.py Added: trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/__init__.py =================================================================== Added: trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/base.py =================================================================== --- trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/base.py (rev 0) +++ trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/base.py 2008-01-09 23:30:44 UTC (rev 4012) @@ -0,0 +1,264 @@ +""" +MySQL database backend for Django. + +Requires MySQLdb: http://sourceforge.net/projects/mysql-python +""" + +from django.db.backends import util +try: + import com.ziclix.python.sql.zxJDBC 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. + +###FJW commenting out version check for now. +###version = Database.version_info +###if (version < (1,2,1) or (version[:3] == (1, 2, 1) and +### (len(version) < 5 or version[3] != 'final' or version[4] < 2))): +### raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__ + +###FJW commenting these out for now. +###from MySQLdb.converters import conversions +###from MySQLdb.constants import FIELD_TYPE +import types +import re + +DatabaseError = Database.DatabaseError +IntegrityError = Database.IntegrityError + +# MySQLdb-1.2.1 supports the Python boolean type, and only uses datetime +# module for time-related columns; older versions could have used mx.DateTime +# or strings if there were no datetime module. However, MySQLdb still returns +# TIME columns as timedelta -- they are more like timedelta in terms of actual +# behavior as they are signed and include days -- and Django expects time, so +# we still need to override that. +### FJW: commented out conversions.copy and update for now. +django_conversions = {} ###conversions.copy() +###django_conversions.update({ +### FIELD_TYPE.TIME: util.typecast_time, +### FIELD_TYPE.DECIMAL: util.typecast_decimal, +### FIELD_TYPE.NEWDECIMAL: util.typecast_decimal, +###}) + +# 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. + +try: + # Only exists in Python 2.4+ + from threading import local +except ImportError: + # Import copy of _thread_local.py from Python 2.4 + from django.utils._threading_local import local + +class DatabaseWrapper(local): + def __init__(self, **kwargs): + self.connection = None + self.queries = [] + self.server_version = None + self.options = kwargs + + def _valid_connection(self): + return self.connection is not None + + def cursor(self): + from django.conf import settings + from warnings import filterwarnings + 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 + else: + kwargs['passwd'] = '' + if settings.DATABASE_HOST: + kwargs['host'] = settings.DATABASE_HOST + else: + kwargs['host'] = 'localhost' + if settings.DATABASE_PORT: + kwargs['port'] = int(settings.DATABASE_PORT) + else: + kwargs['port'] = 3306 + kwargs.update(self.options) + ###FJW + ###self.connection = Database.connect(**kwargs) + self.connection = Database.connect('jdbc:mysql://%(host)s:%(port)s/%(db)s' % kwargs, kwargs['user'], kwargs['passwd'], 'org.gjt.mm.mysql.Driver') + cursor = self.connection.cursor() + else: + cursor = self.connection.cursor() + ###FJW: temp + ###if settings.DEBUG: + ### filterwarnings("error", category=Database.Warning) + ### return util.CursorDebugWrapper(cursor, self) + return cursor + + def _commit(self): + if self.connection is not None: + self.connection.commit() + + def _rollback(self): + if self.connection is not None: + try: + self.connection.rollback() + except Database.NotSupportedError: + pass + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + + 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 + +allows_group_by_ordinal = True +allows_unique_and_pk = True +autoindexes_primary_keys = False +needs_datetime_string_cast = True # MySQLdb requires a typecast for dates +needs_upper_for_iops = False +supports_constraints = True +supports_tablespaces = False +uses_case_insensitive_names = False + +def quote_name(name): + if name.startswith("`") and name.endswith("`"): + return name # Quoting once is enough. + return "`%s`" % name + +dictfetchone = util.dictfetchone +dictfetchmany = util.dictfetchmany +dictfetchall = util.dictfetchall + +def get_last_insert_id(cursor, table_name, pk_name): + return cursor.lastrowid + +def get_date_extract_sql(lookup_type, table_name): + # lookup_type is 'year', 'month', 'day' + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name) + +def get_date_trunc_sql(lookup_type, field_name): + # lookup_type is 'year', 'month', 'day' + 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 get_datetime_cast_sql(): + return None + +def get_limit_offset_sql(limit, offset=None): + sql = "LIMIT " + if offset and offset != 0: + sql += "%s," % offset + return sql + str(limit) + +def get_random_function_sql(): + return "RAND()" + +def get_deferrable_sql(): + return "" + +def get_fulltext_search_sql(field_name): + return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name + +def get_drop_foreignkey_sql(): + return "DROP FOREIGN KEY" + +def get_pk_default_value(): + return "DEFAULT" + +def get_max_name_length(): + return None; + +def get_start_transaction_sql(): + return "BEGIN;" + +def get_autoinc_sql(table): + return None + +def get_sql_flush(style, tables, sequences): + """Return a list of SQL statements required to remove all data from + all tables in the database (without actually removing the tables + themselves) and put the database in an empty 'initial' state + + """ + # 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;'] + \ + ['%s %s;' % \ + (style.SQL_KEYWORD('TRUNCATE'), + style.SQL_FIELD(quote_name(table)) + ) for table in tables] + \ + ['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(quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences]) + return sql + else: + return [] + +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] + +OPERATOR_MAPPING = { + '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', +} Added: trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/client.py =================================================================== --- trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/client.py (rev 0) +++ trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/client.py 2008-01-09 23:30:44 UTC (rev 4012) @@ -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) Added: trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/creation.py =================================================================== --- trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/creation.py (rev 0) +++ trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/creation.py 2008-01-09 23:30:44 UTC (rev 4012) @@ -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(%(maxlength)s)', + 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', + 'DateField': 'date', + 'DateTimeField': 'datetime', + 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'FileField': 'varchar(100)', + 'FilePathField': 'varchar(100)', + 'FloatField': 'double precision', + 'ImageField': 'varchar(100)', + 'IntegerField': 'integer', + 'IPAddressField': 'char(15)', + 'NullBooleanField': 'bool', + 'OneToOneField': 'integer', + 'PhoneNumberField': 'varchar(20)', + 'PositiveIntegerField': 'integer UNSIGNED', + 'PositiveSmallIntegerField': 'smallint UNSIGNED', + 'SlugField': 'varchar(%(maxlength)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'longtext', + 'TimeField': 'time', + 'USStateField': 'varchar(2)', +} Added: trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/introspection.py =================================================================== --- trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/introspection.py (rev 0) +++ trunk/sandbox/jbaker/django/db/backends/jdbc_mysql/introspection.py 2008-01-09 23:30:44 UTC (rev 4012) @@ -0,0 +1,95 @@ +from django.db.backends.jdbc_mysql.base import quote_name +###from MySQLdb import ProgrammingError, OperationalError +###from MySQLdb.constants import FIELD_TYPE +import re + +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: 'CharField', + ###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', +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |