Index: test/utils.py
===================================================================
--- test/utils.py	(revision 6193)
+++ test/utils.py	(working copy)
@@ -1,6 +1,6 @@
 import sys, time
 from django.conf import settings
-from django.db import connection, get_creation_module
+from django.db import connection
 from django.core import mail
 from django.core.management import call_command
 from django.dispatch import dispatcher
@@ -95,9 +95,8 @@
     database already exists. Returns the name of the test database created.
     """
     # If the database backend wants to create the test DB itself, let it
-    creation_module = get_creation_module()
-    if hasattr(creation_module, "create_test_db"):
-        creation_module.create_test_db(settings, connection, verbosity, autoclobber)
+    if hasattr(connection.creation, "create_test_db"):
+        connection.creation.create_test_db(settings, connection, verbosity, autoclobber)
         return
 
     if verbosity >= 1:
@@ -163,9 +162,8 @@
 
 def destroy_test_db(old_database_name, verbosity=1):
     # If the database wants to drop the test DB itself, let it
-    creation_module = get_creation_module()
-    if hasattr(creation_module, "destroy_test_db"):
-        creation_module.destroy_test_db(settings, connection, old_database_name, verbosity)
+    if hasattr(connection.creation, "destroy_test_db"):
+        connection.creation.destroy_test_db(settings, connection, old_database_name, verbosity)
         return
 
     # Unless we're using SQLite, remove the test database to clean up after
Index: db/models/fields/__init__.py
===================================================================
--- db/models/fields/__init__.py	(revision 6231)
+++ db/models/fields/__init__.py	(working copy)
@@ -6,7 +6,6 @@
 except ImportError:
     from django.utils import _decimal as decimal    # for Python 2.3
 
-from django.db import get_creation_module
 from django.db.models import signals
 from django.dispatch import dispatcher
 from django.conf import settings
@@ -145,7 +144,8 @@
         # mapped to one of the built-in Django field types. In this case, you
         # can implement db_type() instead of get_internal_type() to specify
         # exactly which wacky database column type you want to use.
-        data_types = get_creation_module().DATA_TYPES
+        from django.db import connection
+        data_types = connection.creation.data_types
         internal_type = self.get_internal_type()
         return data_types[internal_type] % self.__dict__
 
Index: db/__init__.py
===================================================================
--- db/__init__.py	(revision 6193)
+++ db/__init__.py	(working copy)
@@ -23,8 +23,6 @@
     else:
         raise # If there's some other error, this must be an error in Django itself.
 
-get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
-get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
 runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
 
 connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
@@ -46,4 +44,4 @@
 def _rollback_on_exception():
     from django.db import transaction
     transaction.rollback_unless_managed()
-dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
+dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
\ No newline at end of file
Index: db/backends/ado_mssql/introspection.py
===================================================================
--- db/backends/ado_mssql/introspection.py	(revision 6193)
+++ db/backends/ado_mssql/introspection.py	(working copy)
@@ -1,13 +1,5 @@
-def get_table_list(cursor):
-    raise NotImplementedError
+from django.db.backends.introspection import BaseIntrospection
 
-def get_table_description(cursor, table_name):
-    raise NotImplementedError
-
-def get_relations(cursor, table_name):
-    raise NotImplementedError
-
-def get_indexes(cursor, table_name):
-    raise NotImplementedError
-
-DATA_TYPES_REVERSE = {}
+class Introspection(BaseIntrospection):
+   pass
+   
Index: db/backends/ado_mssql/creation.py
===================================================================
--- db/backends/ado_mssql/creation.py	(revision 6193)
+++ db/backends/ado_mssql/creation.py	(working copy)
@@ -1,25 +1,28 @@
-DATA_TYPES = {
-    'AutoField':         'int IDENTITY (1, 1)',
-    'BooleanField':      'bit',
-    'CharField':         'varchar(%(max_length)s)',
-    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
-    'DateField':         'smalldatetime',
-    'DateTimeField':     'smalldatetime',
-    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
-    'FileField':         'varchar(100)',
-    'FilePathField':     'varchar(100)',
-    'FloatField':        'double precision',
-    'ImageField':        'varchar(100)',
-    'IntegerField':      'int',
-    'IPAddressField':    'char(15)',
-    'NullBooleanField':  'bit',
-    'OneToOneField':     'int',
-    'PhoneNumberField':  'varchar(20)',
-    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
-    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
-    'SlugField':         'varchar(%(max_length)s)',
-    'SmallIntegerField': 'smallint',
-    'TextField':         'text',
-    'TimeField':         'time',
-    'USStateField':      'varchar(2)',
-}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    data_types = {
+        'AutoField':         'int IDENTITY (1, 1)',
+        'BooleanField':      'bit',
+        'CharField':         'varchar(%(max_length)s)',
+        'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
+        'DateField':         'smalldatetime',
+        'DateTimeField':     'smalldatetime',
+        'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
+        'FileField':         'varchar(100)',
+        'FilePathField':     'varchar(100)',
+        'FloatField':        'double precision',
+        'ImageField':        'varchar(100)',
+        'IntegerField':      'int',
+        'IPAddressField':    'char(15)',
+        'NullBooleanField':  'bit',
+        'OneToOneField':     'int',
+        'PhoneNumberField':  'varchar(20)',
+        'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
+        'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
+        'SlugField':         'varchar(%(max_length)s)',
+        'SmallIntegerField': 'smallint',
+        'TextField':         'text',
+        'TimeField':         'time',
+        'USStateField':      'varchar(2)',
+    }
Index: db/backends/mysql_old/introspection.py
===================================================================
--- db/backends/mysql_old/introspection.py	(revision 6193)
+++ db/backends/mysql_old/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.mysql_old.base import DatabaseOperations
 from MySQLdb import ProgrammingError, OperationalError
 from MySQLdb.constants import FIELD_TYPE
@@ -6,91 +7,93 @@
 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()]
+class Introspection(BaseIntrospection):
+    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: 'TextField',
+        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',
+    }
 
-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 get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        cursor.execute("SHOW TABLES")
+        return [row[0] for row in cursor.fetchall()]
 
-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_table_description(self, 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 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())
+    def _name_to_index(self, 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(self.get_table_description(cursor, table_name))])
 
-    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)
+    def get_relations(self, 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 = self._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())
 
-    return relations
+        for my_fieldname, other_table, other_field in constraints:
+            other_field_index = self._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)
 
-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
+        return relations
 
-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: 'TextField',
-    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',
-}
+    def get_indexes(self, 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
+
Index: db/backends/mysql_old/creation.py
===================================================================
--- db/backends/mysql_old/creation.py	(revision 6193)
+++ db/backends/mysql_old/creation.py	(working copy)
@@ -1,29 +1,32 @@
-# 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(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(%(max_length)s)',
-    'SmallIntegerField': 'smallint',
-    'TextField':         'longtext',
-    'TimeField':         'time',
-    'USStateField':      'varchar(2)',
-}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    # 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(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(%(max_length)s)',
+        'SmallIntegerField': 'smallint',
+        'TextField':         'longtext',
+        'TimeField':         'time',
+        'USStateField':      'varchar(2)',
+    }
Index: db/backends/postgresql/introspection.py
===================================================================
--- db/backends/postgresql/introspection.py	(revision 6193)
+++ db/backends/postgresql/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.postgresql.base import DatabaseOperations
 
 quote_name = DatabaseOperations().quote_name
@@ -2,83 +3,83 @@
 
-def get_table_list(cursor):
-    "Returns a list of table names in the current database."
-    cursor.execute("""
-        SELECT c.relname
-        FROM pg_catalog.pg_class c
-        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
-        WHERE c.relkind IN ('r', 'v', '')
-            AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
-            AND pg_catalog.pg_table_is_visible(c.oid)""")
-    return [row[0] for row in cursor.fetchall()]
+class Introspection(BaseIntrospection):
+    data_types_reverse = {
+        16: 'BooleanField',
+        21: 'SmallIntegerField',
+        23: 'IntegerField',
+        25: 'TextField',
+        701: 'FloatField',
+        869: 'IPAddressField',
+        1043: 'CharField',
+        1082: 'DateField',
+        1083: 'TimeField',
+        1114: 'DateTimeField',
+        1184: 'DateTimeField',
+        1266: 'TimeField',
+        1700: 'DecimalField',
+    }
 
-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 get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        cursor.execute("""
+            SELECT c.relname
+            FROM pg_catalog.pg_class c
+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relkind IN ('r', 'v', '')
+                AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+                AND pg_catalog.pg_table_is_visible(c.oid)""")
+        return [row[0] for row in cursor.fetchall()]
 
-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.
-    """
-    cursor.execute("""
-        SELECT con.conkey, con.confkey, c2.relname
-        FROM pg_constraint con, pg_class c1, pg_class c2
-        WHERE c1.oid = con.conrelid
-            AND c2.oid = con.confrelid
-            AND c1.relname = %s
-            AND con.contype = 'f'""", [table_name])
-    relations = {}
-    for row in cursor.fetchall():
-        try:
-            # row[0] and row[1] are like "{2}", so strip the curly braces.
-            relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
-        except ValueError:
-            continue
-    return relations
+    def get_table_description(self, 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 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}
-    """
-    # This query retrieves each index on the given table, including the
-    # first associated field name
-    cursor.execute("""
-        SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
-        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
-            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
-        WHERE c.oid = idx.indrelid
-            AND idx.indexrelid = c2.oid
-            AND attr.attrelid = c.oid
-            AND attr.attnum = idx.indkey[0]
-            AND c.relname = %s""", [table_name])
-    indexes = {}
-    for row in cursor.fetchall():
-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
-        # a string of space-separated integers. This designates the field
-        # indexes (1-based) of the fields that have indexes on the table.
-        # Here, we skip any indexes across multiple fields.
-        if ' ' in row[1]:
-            continue
-        indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
-    return indexes
+    def get_relations(self, 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.
+        """
+        cursor.execute("""
+            SELECT con.conkey, con.confkey, c2.relname
+            FROM pg_constraint con, pg_class c1, pg_class c2
+            WHERE c1.oid = con.conrelid
+                AND c2.oid = con.confrelid
+                AND c1.relname = %s
+                AND con.contype = 'f'""", [table_name])
+        relations = {}
+        for row in cursor.fetchall():
+            try:
+                # row[0] and row[1] are like "{2}", so strip the curly braces.
+                relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
+            except ValueError:
+                continue
+        return relations
 
-# Maps type codes to Django Field types.
-DATA_TYPES_REVERSE = {
-    16: 'BooleanField',
-    21: 'SmallIntegerField',
-    23: 'IntegerField',
-    25: 'TextField',
-    701: 'FloatField',
-    869: 'IPAddressField',
-    1043: 'CharField',
-    1082: 'DateField',
-    1083: 'TimeField',
-    1114: 'DateTimeField',
-    1184: 'DateTimeField',
-    1266: 'TimeField',
-    1700: 'DecimalField',
-}
+    def get_indexes(self, 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}
+        """
+        # This query retrieves each index on the given table, including the
+        # first associated field name
+        cursor.execute("""
+            SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
+            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+                pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
+            WHERE c.oid = idx.indrelid
+                AND idx.indexrelid = c2.oid
+                AND attr.attrelid = c.oid
+                AND attr.attnum = idx.indkey[0]
+                AND c.relname = %s""", [table_name])
+        indexes = {}
+        for row in cursor.fetchall():
+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+            # a string of space-separated integers. This designates the field
+            # indexes (1-based) of the fields that have indexes on the table.
+            # Here, we skip any indexes across multiple fields.
+            if ' ' in row[1]:
+                continue
+            indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
+        return indexes
Index: db/backends/postgresql/creation.py
===================================================================
--- db/backends/postgresql/creation.py	(revision 6193)
+++ db/backends/postgresql/creation.py	(working copy)
@@ -1,29 +1,32 @@
-# This dictionary maps Field objects to their associated PostgreSQL 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':         'serial',
-    'BooleanField':      'boolean',
-    'CharField':         'varchar(%(max_length)s)',
-    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
-    'DateField':         'date',
-    'DateTimeField':     'timestamp with time zone',
-    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
-    'FileField':         'varchar(100)',
-    'FilePathField':     'varchar(100)',
-    'FloatField':        'double precision',
-    'ImageField':        'varchar(100)',
-    'IntegerField':      'integer',
-    'IPAddressField':    'inet',
-    'NullBooleanField':  'boolean',
-    'OneToOneField':     'integer',
-    'PhoneNumberField':  'varchar(20)',
-    'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
-    'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
-    'SlugField':         'varchar(%(max_length)s)',
-    'SmallIntegerField': 'smallint',
-    'TextField':         'text',
-    'TimeField':         'time',
-    'USStateField':      'varchar(2)',
-}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    # This dictionary maps Field objects to their associated PostgreSQL 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':         'serial',
+        'BooleanField':      'boolean',
+        'CharField':         'varchar(%(max_length)s)',
+        'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
+        'DateField':         'date',
+        'DateTimeField':     'timestamp with time zone',
+        'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
+        'FileField':         'varchar(100)',
+        'FilePathField':     'varchar(100)',
+        'FloatField':        'double precision',
+        'ImageField':        'varchar(100)',
+        'IntegerField':      'integer',
+        'IPAddressField':    'inet',
+        'NullBooleanField':  'boolean',
+        'OneToOneField':     'integer',
+        'PhoneNumberField':  'varchar(20)',
+        'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
+        'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
+        'SlugField':         'varchar(%(max_length)s)',
+        'SmallIntegerField': 'smallint',
+        'TextField':         'text',
+        'TimeField':         'time',
+        'USStateField':      'varchar(2)',
+    }
Index: db/backends/sqlite3/introspection.py
===================================================================
--- db/backends/sqlite3/introspection.py	(revision 6193)
+++ db/backends/sqlite3/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.sqlite3.base import DatabaseOperations
 
 quote_name = DatabaseOperations().quote_name
@@ -2,79 +3,24 @@
 
-def get_table_list(cursor):
-    "Returns a list of table names in the current database."
-    # Skip the sqlite_sequence system table used for autoincrement key
-    # generation.
-    cursor.execute("""
-        SELECT name FROM sqlite_master
-        WHERE type='table' AND NOT name='sqlite_sequence'
-        ORDER BY name""")
-    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."
-    return [(info['name'], info['type'], None, None, None, None,
-             info['null_ok']) for info in _table_info(cursor, table_name)]
-
-def get_relations(cursor, table_name):
-    raise NotImplementedError
-
-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}
-    """
-    indexes = {}
-    for info in _table_info(cursor, table_name):
-        indexes[info['name']] = {'primary_key': info['pk'] != 0,
-                                 'unique': False}
-    cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name))
-    # seq, name, unique
-    for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
-        if not unique:
-            continue
-        cursor.execute('PRAGMA index_info(%s)' % quote_name(index))
-        info = cursor.fetchall()
-        # Skip indexes across multiple fields
-        if len(info) != 1:
-            continue
-        name = info[0][2] # seqno, cid, name
-        indexes[name]['unique'] = True
-    return indexes
-
-def _table_info(cursor, name):
-    cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
-    # cid, name, type, notnull, dflt_value, pk
-    return [{'name': field[1],
-             'type': field[2],
-             'null_ok': not field[3],
-             'pk': field[5]     # undocumented
-             } for field in cursor.fetchall()]
-
-# Maps SQL types to Django Field types. Some of the SQL types have multiple
-# entries here because SQLite allows for anything and doesn't normalize the
-# field type; it uses whatever was given.
-BASE_DATA_TYPES_REVERSE = {
-    'bool': 'BooleanField',
-    'boolean': 'BooleanField',
-    'smallint': 'SmallIntegerField',
-    'smallinteger': 'SmallIntegerField',
-    'int': 'IntegerField',
-    'integer': 'IntegerField',
-    'text': 'TextField',
-    'char': 'CharField',
-    'date': 'DateField',
-    'datetime': 'DateTimeField',
-    'time': 'TimeField',
-}
-
 # This light wrapper "fakes" a dictionary interface, because some SQLite data
 # types include variables in them -- e.g. "varchar(30)" -- and can't be matched
 # as a simple dictionary lookup.
 class FlexibleFieldLookupDict:
+    base_data_types_reverse = {
+        'bool': 'BooleanField',
+        'boolean': 'BooleanField',
+        'smallint': 'SmallIntegerField',
+        'smallinteger': 'SmallIntegerField',
+        'int': 'IntegerField',
+        'integer': 'IntegerField',
+        'text': 'TextField',
+        'char': 'CharField',
+        'date': 'DateField',
+        'datetime': 'DateTimeField',
+        'time': 'TimeField',
+    }
+    
     def __getitem__(self, key):
         key = key.lower()
         try:
-            return BASE_DATA_TYPES_REVERSE[key]
+            return self.base_data_types_reverse[key]
         except KeyError:
@@ -86,4 +32,60 @@
                 return ('CharField', {'max_length': int(m.group(1))})
             raise KeyError
 
-DATA_TYPES_REVERSE = FlexibleFieldLookupDict()
+class Introspection(BaseIntrospection):
+    # Maps SQL types to Django Field types. Some of the SQL types have multiple
+    # entries here because SQLite allows for anything and doesn't normalize the
+    # field type; it uses whatever was given.
+    data_types_reverse = FlexibleFieldLookupDict()
+
+    def get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        # Skip the sqlite_sequence system table used for autoincrement key
+        # generation.
+        cursor.execute("""
+            SELECT name FROM sqlite_master
+            WHERE type='table' AND NOT name='sqlite_sequence'
+            ORDER BY name""")
+        return [row[0] for row in cursor.fetchall()]
+
+    def get_table_description(self, cursor, table_name):
+        "Returns a description of the table, with the DB-API cursor.description interface."
+        return [(info['name'], info['type'], None, None, None, None,
+                 info['null_ok']) for info in self._table_info(cursor, table_name)]
+
+    def get_relations(self, cursor, table_name):
+        raise NotImplementedError
+
+    def get_indexes(self, 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}
+        """
+        indexes = {}
+        for info in self._table_info(cursor, table_name):
+            indexes[info['name']] = {'primary_key': info['pk'] != 0,
+                                     'unique': False}
+        cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name))
+        # seq, name, unique
+        for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
+            if not unique:
+                continue
+            cursor.execute('PRAGMA index_info(%s)' % quote_name(index))
+            info = cursor.fetchall()
+            # Skip indexes across multiple fields
+            if len(info) != 1:
+                continue
+            name = info[0][2] # seqno, cid, name
+            indexes[name]['unique'] = True
+        return indexes
+
+    def _table_info(self, cursor, name):
+        cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
+        # cid, name, type, notnull, dflt_value, pk
+        return [{'name': field[1],
+                 'type': field[2],
+                 'null_ok': not field[3],
+                 'pk': field[5]     # undocumented
+                 } for field in cursor.fetchall()]
Index: db/backends/sqlite3/creation.py
===================================================================
--- db/backends/sqlite3/creation.py	(revision 6193)
+++ db/backends/sqlite3/creation.py	(working copy)
@@ -1,28 +1,31 @@
 # SQLite doesn't actually support most of these types, but it "does the right
 # thing" given more verbose field definitions, so leave them as is so that
 # schema inspection is more useful.
-DATA_TYPES = {
-    'AutoField':                    'integer',
-    'BooleanField':                 'bool',
-    'CharField':                    'varchar(%(max_length)s)',
-    'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',
-    'DateField':                    'date',
-    'DateTimeField':                'datetime',
-    'DecimalField':                 'decimal',
-    'FileField':                    'varchar(100)',
-    'FilePathField':                'varchar(100)',
-    'FloatField':                   'real',
-    'ImageField':                   'varchar(100)',
-    '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':                    'text',
-    'TimeField':                    'time',
-    'USStateField':                 'varchar(2)',
-}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    data_types = {
+        'AutoField':                    'integer',
+        'BooleanField':                 'bool',
+        'CharField':                    'varchar(%(max_length)s)',
+        'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',
+        'DateField':                    'date',
+        'DateTimeField':                'datetime',
+        'DecimalField':                 'decimal',
+        'FileField':                    'varchar(100)',
+        'FilePathField':                'varchar(100)',
+        'FloatField':                   'real',
+        'ImageField':                   'varchar(100)',
+        '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':                    'text',
+        'TimeField':                    'time',
+        'USStateField':                 'varchar(2)',
+    }
Index: db/backends/mysql/introspection.py
===================================================================
--- db/backends/mysql/introspection.py	(revision 6193)
+++ db/backends/mysql/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.mysql.base import DatabaseOperations
 from MySQLdb import ProgrammingError, OperationalError
 from MySQLdb.constants import FIELD_TYPE
@@ -6,91 +7,92 @@
 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()]
+class Introspection(BaseIntrospection):
+    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',
+    }
 
-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 get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        cursor.execute("SHOW TABLES")
+        return [row[0] for row in cursor.fetchall()]
 
-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_table_description(self, 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 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())
+    def _name_to_index(self, 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(self.get_table_description(cursor, table_name))])
 
-    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)
+    def get_relations(self, 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 = self._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())
 
-    return relations
+        for my_fieldname, other_table, other_field in constraints:
+            other_field_index = self._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)
 
-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
+        return relations
 
-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',
-}
+    def get_indexes(self, 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
Index: db/backends/mysql/creation.py
===================================================================
--- db/backends/mysql/creation.py	(revision 6193)
+++ db/backends/mysql/creation.py	(working copy)
@@ -1,29 +1,32 @@
-# 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(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(%(max_length)s)',
-    'SmallIntegerField': 'smallint',
-    'TextField':         'longtext',
-    'TimeField':         'time',
-    'USStateField':      'varchar(2)',
-}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    # 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(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(%(max_length)s)',
+        'SmallIntegerField': 'smallint',
+        'TextField':         'longtext',
+        'TimeField':         'time',
+        'USStateField':      'varchar(2)',
+    }
Index: db/backends/oracle/introspection.py
===================================================================
--- db/backends/oracle/introspection.py	(revision 6193)
+++ db/backends/oracle/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.oracle.base import DatabaseOperations
 import re
 import cx_Oracle
@@ -5,58 +6,62 @@
 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("SELECT TABLE_NAME FROM USER_TABLES")
-    return [row[0].upper() 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 WHERE ROWNUM < 2" % 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.
-    """
-    cursor.execute("""
+class BaseIntrospection(BaseIntrospection):
+    data_types_reverse = {
+        cx_Oracle.CLOB: 'TextField',
+        cx_Oracle.DATETIME: 'DateTimeField',
+        cx_Oracle.FIXED_CHAR: 'CharField',
+        cx_Oracle.NCLOB: 'TextField',
+        cx_Oracle.NUMBER: 'DecimalField',
+        cx_Oracle.STRING: 'CharField',
+        cx_Oracle.TIMESTAMP: 'DateTimeField',
+    }
+        
+    def get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
+        return [row[0].upper() for row in cursor.fetchall()]
+    
+    def get_table_description(self, cursor, table_name):
+        "Returns a description of the table, with the DB-API cursor.description interface."
+        cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % quote_name(table_name))
+        return cursor.description
+        
+    def get_relations(self, 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.
+        """
+        cursor.execute("""
 SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
-FROM   user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
-       user_tab_cols ta, user_tab_cols tb
+FROM user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
+        user_tab_cols ta, user_tab_cols tb
 WHERE  user_constraints.table_name = %s AND
-       ta.table_name = %s AND
-       ta.column_name = ca.column_name AND
-       ca.table_name = %s AND
-       user_constraints.constraint_name = ca.constraint_name AND
-       user_constraints.r_constraint_name = cb.constraint_name AND
-       cb.table_name = tb.table_name AND
-       cb.column_name = tb.column_name AND
-       ca.position = cb.position""", [table_name, table_name, table_name])
-
-    relations = {}
-    for row in cursor.fetchall():
-        relations[row[0]] = (row[2], row[1])
-    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}
-    """
-    # This query retrieves each index on the given table, including the
-    # first associated field name
-    # "We were in the nick of time; you were in great peril!"
-    sql = """
+        ta.table_name = %s AND
+        ta.column_name = ca.column_name AND
+        ca.table_name = %s AND
+        user_constraints.constraint_name = ca.constraint_name AND
+        user_constraints.r_constraint_name = cb.constraint_name AND
+        cb.table_name = tb.table_name AND
+        cb.column_name = tb.column_name AND
+        ca.position = cb.position""", [table_name, table_name, table_name])
+        
+        relations = {}
+        for row in cursor.fetchall():
+            relations[row[0]] = (row[2], row[1])
+        return relations
+    
+    def get_indexes(self, 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}
+        """
+        # This query retrieves each index on the given table, including the
+        # first associated field name
+        # "We were in the nick of time; you were in great peril!"
+        sql = """
 WITH primarycols AS (
  SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
  FROM   user_cons_columns, user_constraints
@@ -76,23 +81,19 @@
 WHERE  allcols.column_name = primarycols.column_name (+) AND
       allcols.column_name = uniquecols.column_name (+)
     """
-    cursor.execute(sql, [table_name, table_name])
-    indexes = {}
-    for row in cursor.fetchall():
-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
-        # a string of space-separated integers. This designates the field
-        # indexes (1-based) of the fields that have indexes on the table.
-        # Here, we skip any indexes across multiple fields.
-        indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
-    return indexes
-
-# Maps type objects to Django Field types.
-DATA_TYPES_REVERSE = {
-    cx_Oracle.CLOB: 'TextField',
-    cx_Oracle.DATETIME: 'DateTimeField',
-    cx_Oracle.FIXED_CHAR: 'CharField',
-    cx_Oracle.NCLOB: 'TextField',
-    cx_Oracle.NUMBER: 'DecimalField',
-    cx_Oracle.STRING: 'CharField',
-    cx_Oracle.TIMESTAMP: 'DateTimeField',
-}
+        cursor.execute(sql, [table_name, table_name])
+        indexes = {}
+        for row in cursor.fetchall():
+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+            # a string of space-separated integers. This designates the field
+            # indexes (1-based) of the fields that have indexes on the table.
+            # Here, we skip any indexes across multiple fields.
+            indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
+        return indexes
+        
+    def _name_to_index(self, 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(self.get_table_description(cursor, table_name))])
Index: db/backends/oracle/creation.py
===================================================================
--- db/backends/oracle/creation.py	(revision 6193)
+++ db/backends/oracle/creation.py	(working copy)
@@ -1,153 +1,156 @@
 import sys, time
 from django.core import management
+from django.db.backends.creation import BaseCreation
 
-# This dictionary maps Field objects to their associated Oracle 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':                    'NUMBER(11)',
-    'BooleanField':                 'NUMBER(1) CHECK (%(column)s IN (0,1))',
-    'CharField':                    'NVARCHAR2(%(max_length)s)',
-    'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',
-    'DateField':                    'DATE',
-    'DateTimeField':                'TIMESTAMP',
-    'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
-    'FileField':                    'NVARCHAR2(100)',
-    'FilePathField':                'NVARCHAR2(100)',
-    'FloatField':                   'DOUBLE PRECISION',
-    'ImageField':                   'NVARCHAR2(100)',
-    'IntegerField':                 'NUMBER(11)',
-    'IPAddressField':               'VARCHAR2(15)',
-    'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
-    'OneToOneField':                'NUMBER(11)',
-    'PhoneNumberField':             'VARCHAR2(20)',
-    'PositiveIntegerField':         'NUMBER(11) CHECK (%(column)s >= 0)',
-    'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(column)s >= 0)',
-    'SlugField':                    'NVARCHAR2(50)',
-    'SmallIntegerField':            'NUMBER(11)',
-    'TextField':                    'NCLOB',
-    'TimeField':                    'TIMESTAMP',
-    'URLField':                     'VARCHAR2(200)',
-    'USStateField':                 'CHAR(2)',
-}
+class Creation(BaseCreation):
+    # This dictionary maps Field objects to their associated Oracle 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':                    'NUMBER(11)',
+        'BooleanField':                 'NUMBER(1) CHECK (%(column)s IN (0,1))',
+        'CharField':                    'NVARCHAR2(%(max_length)s)',
+        'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',
+        'DateField':                    'DATE',
+        'DateTimeField':                'TIMESTAMP',
+        'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
+        'FileField':                    'NVARCHAR2(100)',
+        'FilePathField':                'NVARCHAR2(100)',
+        'FloatField':                   'DOUBLE PRECISION',
+        'ImageField':                   'NVARCHAR2(100)',
+        'IntegerField':                 'NUMBER(11)',
+        'IPAddressField':               'VARCHAR2(15)',
+        'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
+        'OneToOneField':                'NUMBER(11)',
+        'PhoneNumberField':             'VARCHAR2(20)',
+        'PositiveIntegerField':         'NUMBER(11) CHECK (%(column)s >= 0)',
+        'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(column)s >= 0)',
+        'SlugField':                    'NVARCHAR2(50)',
+        'SmallIntegerField':            'NUMBER(11)',
+        'TextField':                    'NCLOB',
+        'TimeField':                    'TIMESTAMP',
+        'URLField':                     'VARCHAR2(200)',
+        'USStateField':                 'CHAR(2)',
+    }
 
-TEST_DATABASE_PREFIX = 'test_'
-PASSWORD = 'Im_a_lumberjack'
-REMEMBER = {}
+    def create_test_db(settings, connection, verbosity=1, autoclobber=False):
+        TEST_DATABASE_NAME = _test_database_name(settings)
+        TEST_DATABASE_USER = _test_database_user(settings)
+        TEST_DATABASE_PASSWD = _test_database_passwd(settings)
+        TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
+        TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
 
-def create_test_db(settings, connection, verbosity=1, autoclobber=False):
-    TEST_DATABASE_NAME = _test_database_name(settings)
-    TEST_DATABASE_USER = _test_database_user(settings)
-    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
-    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
-    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
+        parameters = {
+            'dbname': TEST_DATABASE_NAME,
+            'user': TEST_DATABASE_USER,
+            'password': TEST_DATABASE_PASSWD,
+            'tblspace': TEST_DATABASE_TBLSPACE,
+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
+        }
 
-    parameters = {
-        'dbname': TEST_DATABASE_NAME,
-        'user': TEST_DATABASE_USER,
-        'password': TEST_DATABASE_PASSWD,
-        'tblspace': TEST_DATABASE_TBLSPACE,
-        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
- 	}
+        REMEMBER['user'] = settings.DATABASE_USER
+        REMEMBER['passwd'] = settings.DATABASE_PASSWORD
 
-    REMEMBER['user'] = settings.DATABASE_USER
-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
+        cursor = connection.cursor()
+        if _test_database_create(settings):
+            if verbosity >= 1:
+                print 'Creating test database...'
+            try:
+                _create_test_db(cursor, parameters, verbosity)
+            except Exception, e:
+                sys.stderr.write("Got an error creating the test database: %s\n" % e)
+                if not autoclobber:
+                    confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
+                if autoclobber or confirm == 'yes':
+                    try:
+                        if verbosity >= 1:
+                            print "Destroying old test database..."
+                        _destroy_test_db(cursor, parameters, verbosity)
+                        if verbosity >= 1:
+                            print "Creating test database..."
+                        _create_test_db(cursor, parameters, verbosity)
+                    except Exception, e:
+                        sys.stderr.write("Got an error recreating the test database: %s\n" % e)
+                        sys.exit(2)
+                else:
+                    print "Tests cancelled."
+                    sys.exit(1)
 
-    cursor = connection.cursor()
-    if _test_database_create(settings):
-        if verbosity >= 1:
-            print 'Creating test database...'
-        try:
-            _create_test_db(cursor, parameters, verbosity)
-        except Exception, e:
-            sys.stderr.write("Got an error creating the test database: %s\n" % e)
-            if not autoclobber:
-                confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
-            if autoclobber or confirm == 'yes':
-                try:
-                    if verbosity >= 1:
-                        print "Destroying old test database..."
-                    _destroy_test_db(cursor, parameters, verbosity)
-                    if verbosity >= 1:
-                        print "Creating test database..."
-                    _create_test_db(cursor, parameters, verbosity)
-                except Exception, e:
-                    sys.stderr.write("Got an error recreating the test database: %s\n" % e)
-                    sys.exit(2)
-            else:
-                print "Tests cancelled."
-                sys.exit(1)
+        if _test_user_create(settings):
+            if verbosity >= 1:
+                print "Creating test user..."
+            try:
+                _create_test_user(cursor, parameters, verbosity)
+            except Exception, e:
+                sys.stderr.write("Got an error creating the test user: %s\n" % e)
+                if not autoclobber:
+                    confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
+                if autoclobber or confirm == 'yes':
+                    try:
+                        if verbosity >= 1:
+                            print "Destroying old test user..."
+                        _destroy_test_user(cursor, parameters, verbosity)
+                        if verbosity >= 1:
+                            print "Creating test user..."
+                        _create_test_user(cursor, parameters, verbosity)
+                    except Exception, e:
+                        sys.stderr.write("Got an error recreating the test user: %s\n" % e)
+                        sys.exit(2)
+                else:
+                    print "Tests cancelled."
+                    sys.exit(1)
 
-    if _test_user_create(settings):
-        if verbosity >= 1:
-            print "Creating test user..."
-        try:
-            _create_test_user(cursor, parameters, verbosity)
-        except Exception, e:
-            sys.stderr.write("Got an error creating the test user: %s\n" % e)
-            if not autoclobber:
-                confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
-            if autoclobber or confirm == 'yes':
-                try:
-                    if verbosity >= 1:
-                        print "Destroying old test user..."
-                    _destroy_test_user(cursor, parameters, verbosity)
-                    if verbosity >= 1:
-                        print "Creating test user..."
-                    _create_test_user(cursor, parameters, verbosity)
-                except Exception, e:
-                    sys.stderr.write("Got an error recreating the test user: %s\n" % e)
-                    sys.exit(2)
-            else:
-                print "Tests cancelled."
-                sys.exit(1)
+        connection.close()
+        settings.DATABASE_USER = TEST_DATABASE_USER
+        settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
 
-    connection.close()
-    settings.DATABASE_USER = TEST_DATABASE_USER
-    settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
+        management.call_command('syncdb', verbosity=verbosity, interactive=False)
 
-    management.call_command('syncdb', verbosity=verbosity, interactive=False)
+        # Get a cursor (even though we don't need one yet). This has
+        # the side effect of initializing the test database.
+        cursor = connection.cursor()
 
-    # Get a cursor (even though we don't need one yet). This has
-    # the side effect of initializing the test database.
-    cursor = connection.cursor()
+    def destroy_test_db(settings, connection, old_database_name, verbosity=1):
+        connection.close()
 
-def destroy_test_db(settings, connection, old_database_name, verbosity=1):
-    connection.close()
+        TEST_DATABASE_NAME = _test_database_name(settings)
+        TEST_DATABASE_USER = _test_database_user(settings)
+        TEST_DATABASE_PASSWD = _test_database_passwd(settings)
+        TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
+        TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
 
-    TEST_DATABASE_NAME = _test_database_name(settings)
-    TEST_DATABASE_USER = _test_database_user(settings)
-    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
-    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
-    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
+        settings.DATABASE_NAME = old_database_name
+        settings.DATABASE_USER = REMEMBER['user']
+        settings.DATABASE_PASSWORD = REMEMBER['passwd']
 
-    settings.DATABASE_NAME = old_database_name
-    settings.DATABASE_USER = REMEMBER['user']
-    settings.DATABASE_PASSWORD = REMEMBER['passwd']
+        parameters = {
+            'dbname': TEST_DATABASE_NAME,
+            'user': TEST_DATABASE_USER,
+            'password': TEST_DATABASE_PASSWD,
+            'tblspace': TEST_DATABASE_TBLSPACE,
+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
+        }
 
-    parameters = {
-        'dbname': TEST_DATABASE_NAME,
-        'user': TEST_DATABASE_USER,
-        'password': TEST_DATABASE_PASSWD,
-        'tblspace': TEST_DATABASE_TBLSPACE,
-        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
- 	}
+        REMEMBER['user'] = settings.DATABASE_USER
+        REMEMBER['passwd'] = settings.DATABASE_PASSWORD
 
-    REMEMBER['user'] = settings.DATABASE_USER
-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
+        cursor = connection.cursor()
+        time.sleep(1) # To avoid "database is being accessed by other users" errors.
+        if _test_user_create(settings):
+            if verbosity >= 1:
+                print 'Destroying test user...'
+            _destroy_test_user(cursor, parameters, verbosity)
+        if _test_database_create(settings):
+            if verbosity >= 1:
+                print 'Destroying test database...'
+            _destroy_test_db(cursor, parameters, verbosity)
+        connection.close()
 
-    cursor = connection.cursor()
-    time.sleep(1) # To avoid "database is being accessed by other users" errors.
-    if _test_user_create(settings):
-        if verbosity >= 1:
-            print 'Destroying test user...'
-        _destroy_test_user(cursor, parameters, verbosity)
-    if _test_database_create(settings):
-        if verbosity >= 1:
-            print 'Destroying test database...'
-        _destroy_test_db(cursor, parameters, verbosity)
-    connection.close()
+# Seriously?
+TEST_DATABASE_PREFIX = 'test_'
+PASSWORD = 'Im_a_lumberjack'
+REMEMBER = {}
 
 def _create_test_db(cursor, parameters, verbosity):
     if verbosity >= 2:
Index: db/backends/__init__.py
===================================================================
--- db/backends/__init__.py	(revision 6193)
+++ db/backends/__init__.py	(working copy)
@@ -38,6 +38,22 @@
     def make_debug_cursor(self, cursor):
         from django.db.backends import util
         return util.CursorDebugWrapper(cursor, self)
+        
+    def _get_creation(self):
+        if not hasattr(self, '_creation'):
+            from django.conf import settings
+            module = __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
+            self._creation = module.Creation()
+        return self._creation
+    creation = property(fget=_get_creation)
+        
+    def _get_introspection(self):
+        if not hasattr(self, '_introspection'):
+            from django.conf import settings
+            module = __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
+            self._introspection = module.Introspection()
+        return self._introspection
+    introspection = property(_get_introspection)
 
 class BaseDatabaseFeatures(object):
     allows_group_by_ordinal = True
Index: db/backends/postgresql_psycopg2/introspection.py
===================================================================
--- db/backends/postgresql_psycopg2/introspection.py	(revision 6193)
+++ db/backends/postgresql_psycopg2/introspection.py	(working copy)
@@ -1,3 +1,4 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
 
 quote_name = DatabaseOperations().quote_name
@@ -2,80 +3,81 @@
 
-def get_table_list(cursor):
-    "Returns a list of table names in the current database."
-    cursor.execute("""
-        SELECT c.relname
-        FROM pg_catalog.pg_class c
-        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
-        WHERE c.relkind IN ('r', 'v', '')
-            AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
-            AND pg_catalog.pg_table_is_visible(c.oid)""")
-    return [row[0] for row in cursor.fetchall()]
+class Introspection(BaseIntrospection):
+    data_types_reverse = {
+        16: 'BooleanField',
+        21: 'SmallIntegerField',
+        23: 'IntegerField',
+        25: 'TextField',
+        701: 'FloatField',
+        869: 'IPAddressField',
+        1043: 'CharField',
+        1082: 'DateField',
+        1083: 'TimeField',
+        1114: 'DateTimeField',
+        1184: 'DateTimeField',
+        1266: 'TimeField',
+        1700: 'DecimalField',
+    }
+    
+    def get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        cursor.execute("""
+            SELECT c.relname
+            FROM pg_catalog.pg_class c
+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relkind IN ('r', 'v', '')
+                AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+                AND pg_catalog.pg_table_is_visible(c.oid)""")
+        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 get_table_description(self, 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 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.
-    """
-    cursor.execute("""
-        SELECT con.conkey, con.confkey, c2.relname
-        FROM pg_constraint con, pg_class c1, pg_class c2
-        WHERE c1.oid = con.conrelid
-            AND c2.oid = con.confrelid
-            AND c1.relname = %s
-            AND con.contype = 'f'""", [table_name])
-    relations = {}
-    for row in cursor.fetchall():
-        # row[0] and row[1] are single-item lists, so grab the single item.
-        relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
-    return relations
+    def get_relations(self, 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.
+        """
+        cursor.execute("""
+            SELECT con.conkey, con.confkey, c2.relname
+            FROM pg_constraint con, pg_class c1, pg_class c2
+            WHERE c1.oid = con.conrelid
+                AND c2.oid = con.confrelid
+                AND c1.relname = %s
+                AND con.contype = 'f'""", [table_name])
+        relations = {}
+        for row in cursor.fetchall():
+            # row[0] and row[1] are single-item lists, so grab the single item.
+            relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
+        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}
-    """
-    # This query retrieves each index on the given table, including the
-    # first associated field name
-    cursor.execute("""
-        SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
-        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
-            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
-        WHERE c.oid = idx.indrelid
-            AND idx.indexrelid = c2.oid
-            AND attr.attrelid = c.oid
-            AND attr.attnum = idx.indkey[0]
-            AND c.relname = %s""", [table_name])
-    indexes = {}
-    for row in cursor.fetchall():
-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
-        # a string of space-separated integers. This designates the field
-        # indexes (1-based) of the fields that have indexes on the table.
-        # Here, we skip any indexes across multiple fields.
-        if ' ' in row[1]:
-            continue
-        indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
-    return indexes
+    def get_indexes(self, 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}
+        """
+        # This query retrieves each index on the given table, including the
+        # first associated field name
+        cursor.execute("""
+            SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
+            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+                pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
+            WHERE c.oid = idx.indrelid
+                AND idx.indexrelid = c2.oid
+                AND attr.attrelid = c.oid
+                AND attr.attnum = idx.indkey[0]
+                AND c.relname = %s""", [table_name])
+        indexes = {}
+        for row in cursor.fetchall():
+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+            # a string of space-separated integers. This designates the field
+            # indexes (1-based) of the fields that have indexes on the table.
+            # Here, we skip any indexes across multiple fields.
+            if ' ' in row[1]:
+                continue
+            indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
+        return indexes
 
-# Maps type codes to Django Field types.
-DATA_TYPES_REVERSE = {
-    16: 'BooleanField',
-    21: 'SmallIntegerField',
-    23: 'IntegerField',
-    25: 'TextField',
-    701: 'FloatField',
-    869: 'IPAddressField',
-    1043: 'CharField',
-    1082: 'DateField',
-    1083: 'TimeField',
-    1114: 'DateTimeField',
-    1184: 'DateTimeField',
-    1266: 'TimeField',
-    1700: 'DecimalField',
-}
Index: db/backends/dummy/introspection.py
===================================================================
--- db/backends/dummy/introspection.py	(revision 6193)
+++ db/backends/dummy/introspection.py	(working copy)
@@ -1,8 +1,8 @@
+from django.db.backends.introspection import BaseIntrospection
 from django.db.backends.dummy.base import complain
 
-get_table_list = complain
-get_table_description = complain
-get_relations = complain
-get_indexes = complain
-
-DATA_TYPES_REVERSE = {}
+class Introspection(BaseIntrospection):
+    get_table_list = complain
+    get_table_description = complain
+    get_relations = complain
+    get_indexes = complain
Index: db/backends/dummy/creation.py
===================================================================
--- db/backends/dummy/creation.py	(revision 6193)
+++ db/backends/dummy/creation.py	(working copy)
@@ -1 +1,4 @@
-DATA_TYPES = {}
+from django.db.backends.creation import BaseCreation
+
+class Creation(BaseCreation):
+    data_types = {}
\ No newline at end of file
Index: db/backends/creation.py
===================================================================
--- db/backends/creation.py	(revision 6193)
+++ db/backends/creation.py	(working copy)
@@ -4,4 +4,10 @@
     database *creation*, such as the column types to use for particular Django
     Fields.
     """
-    pass
+    data_types = {}     # 'ModelField': 'database_type',
+    
+    def create_test_db(self, settings, connection, verbosity=1, autoclobber=False):
+        raise NotImplementedError()
+        
+    def destroy_test_db(self, settings, connection, old_database_name, verbosity=1):
+        raise NotImplementedError()
\ No newline at end of file
Index: core/management/commands/inspectdb.py
===================================================================
--- core/management/commands/inspectdb.py	(revision 6193)
+++ core/management/commands/inspectdb.py	(working copy)
@@ -13,11 +13,9 @@
             raise CommandError("Database inspection isn't supported for the currently selected database backend.")
 
     def handle_inspection(self):
-        from django.db import connection, get_introspection_module
+        from django.db import connection
         import keyword
 
-        introspection_module = get_introspection_module()
-
         table2model = lambda table_name: table_name.title().replace('_', '')
 
         cursor = connection.cursor()
@@ -32,17 +30,17 @@
         yield ''
         yield 'from django.db import models'
         yield ''
-        for table_name in introspection_module.get_table_list(cursor):
+        for table_name in connection.introspection.get_table_list(cursor):
             yield 'class %s(models.Model):' % table2model(table_name)
             try:
-                relations = introspection_module.get_relations(cursor, table_name)
+                relations = connection.introspection.get_relations(cursor, table_name)
             except NotImplementedError:
                 relations = {}
             try:
-                indexes = introspection_module.get_indexes(cursor, table_name)
+                indexes = connection.introspection.get_indexes(cursor, table_name)
             except NotImplementedError:
                 indexes = {}
-            for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
+            for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
                 att_name = row[0].lower()
                 comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
                 extra_params = {}  # Holds Field parameters such as 'db_column'.
@@ -65,12 +63,12 @@
                         extra_params['db_column'] = att_name
                 else:
                     try:
-                        field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
+                        field_type = connection.introspection.data_types_reverse[row[1]]
                     except KeyError:
                         field_type = 'TextField'
                         comment_notes.append('This field type is a guess.')
 
-                    # This is a hook for DATA_TYPES_REVERSE to return a tuple of
+                    # This is a hook for data_types_reverse to return a tuple of
                     # (field_type, extra_params_dict).
                     if type(field_type) is tuple:
                         field_type, new_params = field_type
Index: core/management/commands/syncdb.py
===================================================================
--- core/management/commands/syncdb.py	(revision 6193)
+++ core/management/commands/syncdb.py	(working copy)
@@ -21,7 +21,7 @@
     def handle_noargs(self, **options):
         from django.db import connection, transaction, models
         from django.conf import settings
-        from django.core.management.sql import table_list, installed_models, sql_model_create, sql_for_pending_references, many_to_many_sql_for_model, custom_sql_for_model, sql_indexes_for_model, emit_post_sync_signal
+        from django.core.management.sql import installed_models, sql_model_create, sql_for_pending_references, many_to_many_sql_for_model, custom_sql_for_model, sql_indexes_for_model, emit_post_sync_signal
 
         verbosity = int(options.get('verbosity', 1))
         interactive = options.get('interactive')
@@ -40,7 +40,7 @@
 
         # Get a list of all existing database tables,
         # so we know what needs to be added.
-        table_list = table_list()
+        table_list = connection.introspection.get_table_list(cursor)
         if connection.features.uses_case_insensitive_names:
             table_name_converter = str.upper
         else:
Index: core/management/sql.py
===================================================================
--- core/management/sql.py	(revision 6193)
+++ core/management/sql.py	(working copy)
@@ -7,12 +7,6 @@
 except NameError:
     from sets import Set as set   # Python 2.3 fallback
 
-def table_list():
-    "Returns a list of all table names that exist in the database."
-    from django.db import connection, get_introspection_module
-    cursor = connection.cursor()
-    return get_introspection_module().get_table_list(cursor)
-
 def django_table_list(only_existing=False):
     """
     Returns a list of all table names that have associated Django models and
@@ -22,13 +16,14 @@
     that actually exist in the database.
     """
     from django.db import models
+    from django.db import connection
     tables = []
     for app in models.get_apps():
         for model in models.get_models(app):
             tables.append(model._meta.db_table)
             tables.extend([f.m2m_db_table() for f in model._meta.many_to_many])
     if only_existing:
-        existing = table_list()
+        existing = connection.introspection.get_table_list(connection.cursor())
         tables = [t for t in tables if t in existing]
     return tables
 
@@ -66,7 +61,7 @@
 
 def sql_create(app, style):
     "Returns a list of the CREATE TABLE SQL statements for the given app."
-    from django.db import models
+    from django.db import connection, models
     from django.conf import settings
 
     if settings.DATABASE_ENGINE == 'dummy':
@@ -82,7 +77,8 @@
     # we can be conservative).
     app_models = models.get_models(app)
     final_output = []
-    known_models = set([model for model in installed_models(table_list()) if model not in app_models])
+    tables = connection.introspection.get_table_list(connection.cursor())
+    known_models = set([model for model in installed_models(tables) if model not in app_models])
     pending_references = {}
 
     for model in app_models:
@@ -114,9 +110,8 @@
 
 def sql_delete(app, style):
     "Returns a list of the DROP TABLE SQL statements for the given app."
-    from django.db import connection, models, get_introspection_module
+    from django.db import connection, models
     from django.db.backends.util import truncate_name
-    introspection = get_introspection_module()
 
     # This should work even if a connection isn't available
     try:
@@ -126,7 +121,7 @@
 
     # Figure out which tables already exist
     if cursor:
-        table_names = introspection.get_table_list(cursor)
+        table_names = connection.introspection.get_table_list(cursor)
     else:
         table_names = []
     if connection.features.uses_case_insensitive_names:
@@ -211,7 +206,7 @@
     if only_django:
         tables = django_table_list()
     else:
-        tables = table_list()
+        tables = connection.introspection.get_table_list(connection.cursor())
     statements = connection.ops.sql_flush(style, tables, sequence_list())
     return statements
 
