app/django/db/backends/mysql/base.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- a/app/django/db/backends/mysql/base.py	Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/db/backends/mysql/base.py	Tue Oct 14 16:00:59 2008 +0000
@@ -4,7 +4,8 @@
 Requires MySQLdb: http://sourceforge.net/projects/mysql-python
 """
 
-from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
+import re
+
 try:
     import MySQLdb as Database
 except ImportError, e:
@@ -21,8 +22,13 @@
     raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
 
 from MySQLdb.converters import conversions
-from MySQLdb.constants import FIELD_TYPE
-import re
+from MySQLdb.constants import FIELD_TYPE, FLAG
+
+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
 
 # Raise exceptions for database warnings if DEBUG is on
 from django.conf import settings
@@ -59,11 +65,52 @@
 # standard util.CursorDebugWrapper can be used. Also, using sql_mode
 # TRADITIONAL will automatically cause most warnings to be treated as errors.
 
+class CursorWrapper(object):
+    """
+    A thin wrapper around MySQLdb's normal cursor class so that we can catch
+    particular exception instances and reraise them with the right types.
+
+    Implemented as a wrapper, rather than a subclass, so that we aren't stuck
+    to the particular underlying representation returned by Connection.cursor().
+    """
+    codes_for_integrityerror = (1048,)
+
+    def __init__(self, cursor):
+        self.cursor = cursor
+
+    def execute(self, query, args=None):
+        try:
+            return self.cursor.execute(query, args)
+        except Database.OperationalError, e:
+            # Map some error codes to IntegrityError, since they seem to be
+            # misclassified and Django would prefer the more logical place.
+            if e[0] in self.codes_for_integrityerror:
+                raise Database.IntegrityError(tuple(e))
+            raise
+
+    def executemany(self, query, args):
+        try:
+            return self.cursor.executemany(query, args)
+        except Database.OperationalError, e:
+            # Map some error codes to IntegrityError, since they seem to be
+            # misclassified and Django would prefer the more logical place.
+            if e[0] in self.codes_for_integrityerror:
+                raise Database.IntegrityError(tuple(e))
+            raise
+
+    def __getattr__(self, attr):
+        if attr in self.__dict__:
+            return self.__dict__[attr]
+        else:
+            return getattr(self.cursor, attr)
+
+    def __iter__(self):
+        return iter(self.cursor)
+
 class DatabaseFeatures(BaseDatabaseFeatures):
-    autoindexes_primary_keys = False
-    inline_fk_references = False
     empty_fetchmany_value = ()
     update_can_self_select = False
+    related_fields_match_type = True
 
 class DatabaseOperations(BaseDatabaseOperations):
     def date_extract_sql(self, lookup_type, field_name):
@@ -89,13 +136,6 @@
     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 no_limit_value(self):
         # 2**64 - 1, as recommended by the MySQL documentation
         return 18446744073709551615L
@@ -131,9 +171,36 @@
         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):
-    features = DatabaseFeatures()
-    ops = DatabaseOperations()
+
     operators = {
         'exact': '= %s',
         'iexact': 'LIKE %s',
@@ -155,6 +222,13 @@
         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:
@@ -186,7 +260,7 @@
                 kwargs['port'] = int(settings.DATABASE_PORT)
             kwargs.update(self.options)
             self.connection = Database.connect(**kwargs)
-        cursor = self.connection.cursor()
+        cursor = CursorWrapper(self.connection.cursor())
         return cursor
 
     def _rollback(self):