root/CodeSnippets/django_backends_mysql_old/base.py

Revision 1692, 9.6 KB (checked in by JensDiemer, 20 months ago)

Patched svn revision 7852:
 http://code.djangoproject.com/changeset/7852
 http://code.djangoproject.com/browser/django/trunk/django/db/backends/mysql_old/base.py?rev=7852

  • Property svn:eol-style set to LF
Line 
1"""
2MySQL database backend for Django.
3
4Requires MySQLdb: http://sourceforge.net/projects/mysql-python
5
6Patched svn revision 7852:
7http://code.djangoproject.com/changeset/7852
8http://code.djangoproject.com/browser/django/trunk/django/db/backends/mysql_old/base.py?rev=7852
9"""
10
11from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
12from django.utils.encoding import force_unicode
13try:
14    import MySQLdb as Database
15except ImportError, e:
16    from django.core.exceptions import ImproperlyConfigured
17    raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
18from MySQLdb.converters import conversions
19from MySQLdb.constants import FIELD_TYPE
20import types
21import re
22
23DatabaseError = Database.DatabaseError
24IntegrityError = Database.IntegrityError
25
26django_conversions = conversions.copy()
27django_conversions.update({
28    types.BooleanType: util.rev_typecast_boolean,
29    FIELD_TYPE.DATETIME: util.typecast_timestamp,
30    FIELD_TYPE.DATE: util.typecast_date,
31    FIELD_TYPE.TIME: util.typecast_time,
32    FIELD_TYPE.DECIMAL: util.typecast_decimal,
33    FIELD_TYPE.STRING: force_unicode,
34    FIELD_TYPE.VAR_STRING: force_unicode,
35    # Note: We don't add a convertor for BLOB here. Doesn't seem to be required.
36})
37
38# This should match the numerical portion of the version numbers (we can treat
39# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
40# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
41# http://dev.mysql.com/doc/refman/5.0/en/news.html .
42server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
43
44# This is an extra debug layer over MySQL queries, to display warnings.
45# It's only used when DEBUG=True.
46class MysqlDebugWrapper:
47    def __init__(self, cursor):
48        self.cursor = cursor
49
50    def execute(self, sql, params=()):
51        try:
52            return self.cursor.execute(sql, params)
53        except Database.Warning, w:
54            self.cursor.execute("SHOW WARNINGS")
55            raise Database.Warning("%s: %s" % (w, self.cursor.fetchall()))
56
57    def executemany(self, sql, param_list):
58        try:
59            return self.cursor.executemany(sql, param_list)
60        except Database.Warning, w:
61            self.cursor.execute("SHOW WARNINGS")
62            raise Database.Warning("%s: %s" % (w, self.cursor.fetchall()))
63
64    def __getattr__(self, attr):
65        if attr in self.__dict__:
66            return self.__dict__[attr]
67        else:
68            return getattr(self.cursor, attr)
69
70class MysqlUnicodeWrapper:
71    """
72    A Wrapper who decode all byte strings to unicode.
73    """
74    def __init__(self, cursor):
75        self.cursor = cursor
76
77    def _decode_results(self, result_raw):
78        """
79        decode all byte string to unicode with the server encoding.
80        """
81        if not result_raw:
82            return result_raw
83        result = []
84        for item in result_raw:
85            if isinstance(item, str):
86                item = item.decode("utf-8")
87            result.append(item)
88
89        return tuple(result)
90
91    def _decode_lines(self, result_raw):
92        """
93        decode every line in a resultset.
94        """
95        result = []
96        for line in result_raw:
97            result.append(self._decode_results(line))
98        return tuple(result)
99
100    def fetchone(self):
101        result_raw = self.cursor.fetchone()
102        return self._decode_results(result_raw)
103
104    def fetchall(self):
105        result_raw = self.cursor.fetchall()
106        return self._decode_lines(result_raw)
107
108    def fetchmany(self, *args):
109        result_raw = self.cursor.fetchmany(*args)
110        return self._decode_lines(result_raw)
111        return result_raw
112
113    def __getattr__(self, attr):
114        if attr in self.__dict__:
115            return self.__dict__[attr]
116        else:
117            return getattr(self.cursor, attr)
118
119class DatabaseFeatures(BaseDatabaseFeatures):
120    inline_fk_references = False
121    empty_fetchmany_value = ()
122    update_can_self_select = False
123    supports_usecs = False
124
125class DatabaseOperations(BaseDatabaseOperations):
126    def date_extract_sql(self, lookup_type, field_name):
127        # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
128        return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
129
130    def date_trunc_sql(self, lookup_type, field_name):
131        fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
132        format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
133        format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
134        try:
135            i = fields.index(lookup_type) + 1
136        except ValueError:
137            sql = field_name
138        else:
139            format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
140            sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
141        return sql
142
143    def drop_foreignkey_sql(self):
144        return "DROP FOREIGN KEY"
145
146    def fulltext_search_sql(self, field_name):
147        return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
148
149    def no_limit_value(self):
150        # 2**64 - 1, as recommended by the MySQL documentation
151        return 18446744073709551615L
152
153    def quote_name(self, name):
154        if name.startswith("`") and name.endswith("`"):
155            return name # Quoting once is enough.
156        return "`%s`" % name
157
158    def random_function_sql(self):
159        return 'RAND()'
160
161    def sql_flush(self, style, tables, sequences):
162        # NB: The generated SQL below is specific to MySQL
163        # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
164        # to clear all tables of all data
165        if tables:
166            sql = ['SET FOREIGN_KEY_CHECKS = 0;']
167            for table in tables:
168                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
169            sql.append('SET FOREIGN_KEY_CHECKS = 1;')
170
171            # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
172            # to reset sequence indices
173            sql.extend(["%s %s %s %s %s;" % \
174                (style.SQL_KEYWORD('ALTER'),
175                 style.SQL_KEYWORD('TABLE'),
176                 style.SQL_TABLE(self.quote_name(sequence['table'])),
177                 style.SQL_KEYWORD('AUTO_INCREMENT'),
178                 style.SQL_FIELD('= 1'),
179                ) for sequence in sequences])
180            return sql
181        else:
182            return []
183
184class DatabaseWrapper(BaseDatabaseWrapper):
185    features = DatabaseFeatures()
186    ops = DatabaseOperations()
187    operators = {
188        'exact': '= BINARY %s',
189        'iexact': 'LIKE %s',
190        'contains': 'LIKE BINARY %s',
191        'icontains': 'LIKE %s',
192        'regex': 'REGEXP BINARY %s',
193        'iregex': 'REGEXP %s',
194        'gt': '> %s',
195        'gte': '>= %s',
196        'lt': '< %s',
197        'lte': '<= %s',
198        'startswith': 'LIKE BINARY %s',
199        'endswith': 'LIKE BINARY %s',
200        'istartswith': 'LIKE %s',
201        'iendswith': 'LIKE %s',
202    }
203
204    def __init__(self, **kwargs):
205        super(DatabaseWrapper, self).__init__(**kwargs)
206        self.server_version = None
207
208    def _valid_connection(self):
209        if self.connection is not None:
210            try:
211                self.connection.ping()
212                return True
213            except DatabaseError:
214                self.connection.close()
215                self.connection = None
216        return False
217
218    def _cursor(self, settings):
219        if not self._valid_connection():
220            kwargs = {
221                # Note: use_unicode intentonally not set to work around some
222                # backwards-compat issues. We do it manually.
223                'user': settings.DATABASE_USER,
224                'db': settings.DATABASE_NAME,
225                'passwd': settings.DATABASE_PASSWORD,
226                'conv': django_conversions,
227            }
228            if settings.DATABASE_HOST.startswith('/'):
229                kwargs['unix_socket'] = settings.DATABASE_HOST
230            else:
231                kwargs['host'] = settings.DATABASE_HOST
232            if settings.DATABASE_PORT:
233                kwargs['port'] = int(settings.DATABASE_PORT)
234            kwargs.update(self.options)
235            self.connection = Database.connect(**kwargs)
236            cursor = self.connection.cursor()
237            if self.connection.get_server_info() >= '4.1' and not self.connection.character_set_name().startswith('utf8'):
238                if hasattr(self.connection, 'charset'):
239                    # MySQLdb < 1.2.1 backwards-compat hacks.
240                    conn = self.connection
241                    cursor.execute("SET NAMES 'utf8'")
242                    cursor.execute("SET CHARACTER SET 'utf8'")
243                    to_str = lambda u, dummy=None, c=conn: c.literal(u.encode('utf-8'))
244                    conn.converter[unicode] = to_str
245                else:
246                    self.connection.set_character_set('utf8')
247        else:
248            cursor = self.connection.cursor()
249
250        cursor = MysqlUnicodeWrapper(cursor)
251
252        return cursor
253
254    def make_debug_cursor(self, cursor):
255        return BaseDatabaseWrapper.make_debug_cursor(self, MysqlDebugWrapper(cursor))
256
257    def _rollback(self):
258        try:
259            BaseDatabaseWrapper._rollback(self)
260        except Database.NotSupportedError:
261            pass
262
263    def get_server_version(self):
264        if not self.server_version:
265            if not self._valid_connection():
266                self.cursor()
267            m = server_version_re.match(self.connection.get_server_info())
268            if not m:
269                raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
270            self.server_version = tuple([int(x) for x in m.groups()])
271        return self.server_version
Note: See TracBrowser for help on using the browser.