app/django/db/backends/__init__.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- a/app/django/db/backends/__init__.py	Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/db/backends/__init__.py	Tue Oct 14 16:00:59 2008 +0000
@@ -4,6 +4,14 @@
 except ImportError:
     # Import copy of _thread_local.py from Python 2.4
     from django.utils._threading_local import local
+try:
+    set
+except NameError:
+    # Python 2.3 compat
+    from sets import Set as set
+
+from django.db.backends import util
+from django.utils import datetime_safe
 
 class BaseDatabaseWrapper(local):
     """
@@ -23,6 +31,21 @@
         if self.connection is not None:
             return self.connection.rollback()
 
+    def _savepoint(self, sid):
+        if not self.features.uses_savepoints:
+            return
+        self.connection.cursor().execute(self.ops.savepoint_create_sql(sid))
+
+    def _savepoint_rollback(self, sid):
+        if not self.features.uses_savepoints:
+            return
+        self.connection.cursor().execute(self.ops.savepoint_rollback_sql(sid))
+
+    def _savepoint_commit(self, sid):
+        if not self.features.uses_savepoints:
+            return
+        self.connection.cursor().execute(self.ops.savepoint_commit_sql(sid))
+
     def close(self):
         if self.connection is not None:
             self.connection.close()
@@ -36,22 +59,21 @@
         return cursor
 
     def make_debug_cursor(self, cursor):
-        from django.db.backends import util
         return util.CursorDebugWrapper(cursor, self)
 
 class BaseDatabaseFeatures(object):
-    allows_group_by_ordinal = True
-    allows_unique_and_pk = True
-    autoindexes_primary_keys = True
-    inline_fk_references = True
+    # True if django.db.backend.utils.typecast_timestamp is used on values
+    # returned from dates() calls.
     needs_datetime_string_cast = True
-    needs_upper_for_iops = False
-    supports_constraints = True
-    supports_tablespaces = False
-    uses_case_insensitive_names = False
     uses_custom_query_class = False
     empty_fetchmany_value = []
     update_can_self_select = True
+    interprets_empty_strings_as_nulls = False
+    can_use_chunked_reads = True
+    uses_savepoints = False
+    # If True, don't use integer foreign keys referring to, e.g., positive
+    # integer primary keys.
+    related_fields_match_type = False
 
 class BaseDatabaseOperations(object):
     """
@@ -160,16 +182,6 @@
         """
         return cursor.lastrowid
 
-    def limit_offset_sql(self, limit, offset=None):
-        """
-        Returns a LIMIT/OFFSET SQL clause, given a limit and optional offset.
-        """
-        # 'LIMIT 40 OFFSET 20'
-        sql = "LIMIT %s" % limit
-        if offset and offset != 0:
-            sql += " OFFSET %s" % offset
-        return sql
-
     def lookup_cast(self, lookup_type):
         """
         Returns the string to use in a query when performing lookups
@@ -202,8 +214,8 @@
 
     def query_class(self, DefaultQueryClass):
         """
-        Given the default QuerySet class, returns a custom QuerySet class
-        to use for this backend. Returns None if a custom QuerySet isn't used.
+        Given the default Query class, returns a custom Query class
+        to use for this backend. Returns None if a custom Query isn't used.
         See also BaseDatabaseFeatures.uses_custom_query_class, which regulates
         whether this method is called at all.
         """
@@ -233,6 +245,26 @@
         """
         raise NotImplementedError
 
+    def savepoint_create_sql(self, sid):
+        """
+        Returns the SQL for starting a new savepoint. Only required if the
+        "uses_savepoints" feature is True. The "sid" parameter is a string
+        for the savepoint id.
+        """
+        raise NotImplementedError
+
+    def savepoint_commit_sql(self, sid):
+        """
+        Returns the SQL for committing the given savepoint.
+        """
+        raise NotImplementedError
+
+    def savepoint_rollback_sql(self, sid):
+        """
+        Returns the SQL for rolling back the given savepoint.
+        """
+        raise NotImplementedError
+
     def sql_flush(self, style, tables, sequences):
         """
         Returns a list of SQL statements required to remove all data from
@@ -262,7 +294,166 @@
 
     def tablespace_sql(self, tablespace, inline=False):
         """
-        Returns the tablespace SQL, or None if the backend doesn't use
-        tablespaces.
+        Returns the SQL that will be appended to tables or rows to define
+        a tablespace. Returns '' if the backend doesn't use tablespaces.
+        """
+        return ''
+
+    def prep_for_like_query(self, x):
+        """Prepares a value for use in a LIKE query."""
+        from django.utils.encoding import smart_unicode
+        return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+
+    # Same as prep_for_like_query(), but called for "iexact" matches, which
+    # need not necessarily be implemented using "LIKE" in the backend.
+    prep_for_iexact_query = prep_for_like_query
+
+    def value_to_db_date(self, value):
+        """
+        Transform a date value to an object compatible with what is expected
+        by the backend driver for date columns.
+        """
+        if value is None:
+            return None
+        return datetime_safe.new_date(value).strftime('%Y-%m-%d')
+
+    def value_to_db_datetime(self, value):
+        """
+        Transform a datetime value to an object compatible with what is expected
+        by the backend driver for datetime columns.
+        """
+        if value is None:
+            return None
+        return unicode(value)
+
+    def value_to_db_time(self, value):
+        """
+        Transform a datetime value to an object compatible with what is expected
+        by the backend driver for time columns.
+        """
+        if value is None:
+            return None
+        return unicode(value)
+
+    def value_to_db_decimal(self, value, max_digits, decimal_places):
+        """
+        Transform a decimal.Decimal value to an object compatible with what is
+        expected by the backend driver for decimal (numeric) columns.
+        """
+        if value is None:
+            return None
+        return util.format_number(value, max_digits, decimal_places)
+
+    def year_lookup_bounds(self, value):
+        """
+        Returns a two-elements list with the lower and upper bound to be used
+        with a BETWEEN operator to query a field value using a year lookup
+
+        `value` is an int, containing the looked-up year.
+        """
+        first = '%s-01-01 00:00:00'
+        second = '%s-12-31 23:59:59.999999'
+        return [first % value, second % value]
+
+    def year_lookup_bounds_for_date_field(self, value):
+        """
+        Returns a two-elements list with the lower and upper bound to be used
+        with a BETWEEN operator to query a DateField value using a year lookup
+
+        `value` is an int, containing the looked-up year.
+
+        By default, it just calls `self.year_lookup_bounds`. Some backends need
+        this hook because on their DB date fields can't be compared to values
+        which include a time part.
         """
-        return None
+        return self.year_lookup_bounds(value)
+
+class BaseDatabaseIntrospection(object):
+    """
+    This class encapsulates all backend-specific introspection utilities
+    """
+    data_types_reverse = {}
+
+    def __init__(self, connection):
+        self.connection = connection
+
+    def table_name_converter(self, name):
+        """Apply a conversion to the name for the purposes of comparison.
+
+        The default table name converter is for case sensitive comparison.
+        """
+        return name
+
+    def table_names(self):
+        "Returns a list of names of all tables that exist in the database."
+        cursor = self.connection.cursor()
+        return self.get_table_list(cursor)
+
+    def django_table_names(self, only_existing=False):
+        """
+        Returns a list of all table names that have associated Django models and
+        are in INSTALLED_APPS.
+
+        If only_existing is True, the resulting list will only include the tables
+        that actually exist in the database.
+        """
+        from django.db import models
+        tables = set()
+        for app in models.get_apps():
+            for model in models.get_models(app):
+                tables.add(model._meta.db_table)
+                tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
+        if only_existing:
+            tables = [t for t in tables if t in self.table_names()]
+        return tables
+
+    def installed_models(self, tables):
+        "Returns a set of all models represented by the provided list of table names."
+        from django.db import models
+        all_models = []
+        for app in models.get_apps():
+            for model in models.get_models(app):
+                all_models.append(model)
+        return set([m for m in all_models
+            if self.table_name_converter(m._meta.db_table) in map(self.table_name_converter, tables)
+        ])
+
+    def sequence_list(self):
+        "Returns a list of information about all DB sequences for all models in all apps."
+        from django.db import models
+
+        apps = models.get_apps()
+        sequence_list = []
+
+        for app in apps:
+            for model in models.get_models(app):
+                for f in model._meta.local_fields:
+                    if isinstance(f, models.AutoField):
+                        sequence_list.append({'table': model._meta.db_table, 'column': f.column})
+                        break # Only one AutoField is allowed per model, so don't bother continuing.
+
+                for f in model._meta.local_many_to_many:
+                    sequence_list.append({'table': f.m2m_db_table(), 'column': None})
+
+        return sequence_list
+
+class BaseDatabaseClient(object):
+    """
+    This class encapsulates all backend-specific methods for opening a
+    client shell.
+    """
+    # This should be a string representing the name of the executable
+    # (e.g., "psql"). Subclasses must override this.
+    executable_name = None
+
+    def runshell(self):
+        raise NotImplementedError()
+
+class BaseDatabaseValidation(object):
+    """
+    This class encapsualtes all backend-specific model validation.
+    """
+    def validate_field(self, errors, opts, f):
+        "By default, there is no backend-specific validation"
+        pass
+