Index: django/test/utils.py
===================================================================
--- django/test/utils.py	(revision 8156)
+++ django/test/utils.py	(working copy)
@@ -1,6 +1,6 @@
 import sys, time, os
 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
@@ -101,9 +101,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:
@@ -191,9 +190,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
 
     if verbosity >= 1:
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 8156)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -7,7 +7,7 @@
 except ImportError:
     from django.utils import _decimal as decimal    # for Python 2.3
 
-from django.db import connection, get_creation_module
+from django.db import connection
 from django.db.models import signals
 from django.db.models.query_utils import QueryWrapper
 from django.dispatch import dispatcher
@@ -150,14 +150,14 @@
         # as the TextField Django field type, which means XMLField's
         # get_internal_type() returns 'TextField'.
         #
-        # But the limitation of the get_internal_type() / DATA_TYPES approach
+        # But the limitation of the get_internal_type() / data_types approach
         # is that it cannot handle database column types that aren't already
         # 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 = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
         try:
-            return get_creation_module().DATA_TYPES[self.get_internal_type()] % data
+            return connection.creation.data_types[self.get_internal_type()] % data
         except KeyError:
             return None
 
Index: django/db/__init__.py
===================================================================
--- django/db/__init__.py	(revision 8156)
+++ django/db/__init__.py	(working copy)
@@ -15,14 +15,14 @@
     # backends that ships with Django, so look there first.
     _import_path = 'django.db.backends.'
     backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
-    creation = __import__('%s%s.creation' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
 except ImportError, e:
+    import traceback
+    traceback.print_exc()
     # If the import failed, we might be looking for a database backend
     # distributed external to Django. So we'll try that next.
     try:
         _import_path = ''
         backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
-        creation = __import__('%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
     except ImportError, e_user:
         # The database backend wasn't found. Display a helpful error message
         # listing all possible (built-in) database backends.
@@ -35,22 +35,6 @@
         else:
             raise # If there's some other error, this must be an error in Django itself.
 
-def _import_database_module(import_path='', module_name=''):
-    """Lazily import a database module when requested."""
-    return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
-
-# We don't want to import the introspect module unless someone asks for it, so
-# lazily load it on demmand.
-get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
-
-def get_creation_module():
-    return creation
-
-# We want runshell() to work the same way, but we have to treat it a
-# little differently (since it just runs instead of returning a module like
-# the above) and wrap the lazily-loaded runshell() method.
-runshell = lambda: _import_database_module(_import_path, "client").runshell()
-
 # Convenient aliases for backend bits.
 connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
 DatabaseError = backend.DatabaseError
Index: django/db/backends/postgresql/base.py
===================================================================
--- django/db/backends/postgresql/base.py	(revision 8156)
+++ django/db/backends/postgresql/base.py	(working copy)
@@ -7,6 +7,10 @@
 from django.utils.encoding import smart_str, smart_unicode
 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util
 from django.db.backends.postgresql.operations import DatabaseOperations
+from django.db.backends.postgresql.client import DatabaseClient
+from django.db.backends.postgresql.creation import DatabaseCreation
+from django.db.backends.postgresql.introspection import DatabaseIntrospection
+
 try:
     import psycopg as Database
 except ImportError, e:
@@ -65,6 +69,10 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     features = DatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection(ops)
+
     operators = {
         'exact': '= %s',
         'iexact': 'ILIKE %s',
Index: django/db/backends/postgresql/client.py
===================================================================
--- django/db/backends/postgresql/client.py	(revision 8156)
+++ django/db/backends/postgresql/client.py	(working copy)
@@ -1,15 +1,17 @@
+from django.db.backends import BaseDatabaseClient
 from django.conf import settings
 import os
 
-def runshell():
-    args = ['psql']
-    if settings.DATABASE_USER:
-        args += ["-U", settings.DATABASE_USER]
-    if settings.DATABASE_PASSWORD:
-        args += ["-W"]
-    if settings.DATABASE_HOST:
-        args.extend(["-h", settings.DATABASE_HOST])
-    if settings.DATABASE_PORT:
-        args.extend(["-p", str(settings.DATABASE_PORT)])
-    args += [settings.DATABASE_NAME]
-    os.execvp('psql', args)
+class DatabaseClient(BaseDatabaseClient):
+    def runshell(self):
+        args = ['psql']
+        if settings.DATABASE_USER:
+            args += ["-U", settings.DATABASE_USER]
+        if settings.DATABASE_PASSWORD:
+            args += ["-W"]
+        if settings.DATABASE_HOST:
+            args.extend(["-h", settings.DATABASE_HOST])
+        if settings.DATABASE_PORT:
+            args.extend(["-p", str(settings.DATABASE_PORT)])
+        args += [settings.DATABASE_NAME]
+        os.execvp('psql', args)
Index: django/db/backends/postgresql/introspection.py
===================================================================
--- django/db/backends/postgresql/introspection.py	(revision 8156)
+++ django/db/backends/postgresql/introspection.py	(working copy)
@@ -1,86 +1,89 @@
-from django.db.backends.postgresql.base import DatabaseOperations
+from django.db.backends import BaseDatabaseIntrospection
 
-quote_name = DatabaseOperations().quote_name
+class DatabaseIntrospection(BaseDatabaseIntrospection):
+    # 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 __init__(self, ops):
+        self.ops = ops
+        
+    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_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()]
+    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" % self.ops.quote_name(table_name))
+        return cursor.description
 
-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_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
 
-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_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
 
-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
-
-# 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: django/db/backends/postgresql/creation.py
===================================================================
--- django/db/backends/postgresql/creation.py	(revision 8156)
+++ django/db/backends/postgresql/creation.py	(working copy)
@@ -1,28 +1,31 @@
-# 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(%(max_length)s)',
-    'FilePathField':     'varchar(%(max_length)s)',
-    'FloatField':        'double precision',
-    '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 import BaseDatabaseCreation
+
+class DatabaseCreation(BaseDatabaseCreation):
+    # 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(%(max_length)s)',
+        'FilePathField':     'varchar(%(max_length)s)',
+        'FloatField':        'double precision',
+        '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: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(revision 8156)
+++ django/db/backends/sqlite3/base.py	(working copy)
@@ -7,6 +7,10 @@
 """
 
 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
+from django.db.backends.sqlite3.client import DatabaseClient
+from django.db.backends.sqlite3.creation import DatabaseCreation
+from django.db.backends.sqlite3.introspection import DatabaseIntrospection
+
 try:
     try:
         from sqlite3 import dbapi2 as Database
@@ -93,6 +97,9 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     features = DatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection(ops)
 
     # SQLite requires LIKE statements to include an ESCAPE clause if the value
     # being escaped has a percent or underscore in it.
Index: django/db/backends/sqlite3/client.py
===================================================================
--- django/db/backends/sqlite3/client.py	(revision 8156)
+++ django/db/backends/sqlite3/client.py	(working copy)
@@ -1,6 +1,8 @@
+from django.db.backends import BaseDatabaseClient
 from django.conf import settings
 import os
 
-def runshell():
-    args = ['', settings.DATABASE_NAME]
-    os.execvp('sqlite3', args)
+class DatabaseClient(BaseDatabaseClient):
+    def runshell(self):
+        args = ['', settings.DATABASE_NAME]
+        os.execvp('sqlite3', args)
Index: django/db/backends/sqlite3/introspection.py
===================================================================
--- django/db/backends/sqlite3/introspection.py	(revision 8156)
+++ django/db/backends/sqlite3/introspection.py	(working copy)
@@ -1,84 +1,30 @@
-from django.db.backends.sqlite3.base import DatabaseOperations
+from django.db.backends import BaseDatabaseIntrospection
 
-quote_name = DatabaseOperations().quote_name
-
-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:
+    # 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',
+    }
+
     def __getitem__(self, key):
         key = key.lower()
         try:
-            return BASE_DATA_TYPES_REVERSE[key]
+            return self.base_data_types_reverse[key]
         except KeyError:
             import re
             m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
@@ -86,4 +32,61 @@
                 return ('CharField', {'max_length': int(m.group(1))})
             raise KeyError
 
-DATA_TYPES_REVERSE = FlexibleFieldLookupDict()
+class DatabaseIntrospection(BaseDatabaseIntrospection):
+    data_types_reverse = FlexibleFieldLookupDict()
+
+    def __init__(self, ops):
+        self.ops = ops
+        
+    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)' % self.ops.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)' % self.ops.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)' % self.ops.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: django/db/backends/sqlite3/creation.py
===================================================================
--- django/db/backends/sqlite3/creation.py	(revision 8156)
+++ django/db/backends/sqlite3/creation.py	(working copy)
@@ -1,27 +1,30 @@
-# 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(%(max_length)s)',
-    'FilePathField':                'varchar(%(max_length)s)',
-    'FloatField':                   'real',
-    '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 import BaseDatabaseCreation
+
+class DatabaseCreation(BaseDatabaseCreation):
+    # 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(%(max_length)s)',
+        'FilePathField':                'varchar(%(max_length)s)',
+        'FloatField':                   'real',
+        '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: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(revision 8156)
+++ django/db/backends/mysql/base.py	(working copy)
@@ -5,6 +5,10 @@
 """
 
 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
+from django.db.backends.mysql.client import DatabaseClient
+from django.db.backends.mysql.creation import DatabaseCreation
+from django.db.backends.mysql.introspection import DatabaseIntrospection
+
 try:
     import MySQLdb as Database
 except ImportError, e:
@@ -144,6 +148,10 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     features = DatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection(ops)
+    
     operators = {
         'exact': '= BINARY %s',
         'iexact': 'LIKE %s',
Index: django/db/backends/mysql/client.py
===================================================================
--- django/db/backends/mysql/client.py	(revision 8156)
+++ django/db/backends/mysql/client.py	(working copy)
@@ -1,27 +1,29 @@
+from django.db.backends import BaseDatabaseClient
 from django.conf import settings
 import os
 
-def runshell():
-    args = ['']
-    db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
-    user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
-    passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
-    host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
-    port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
-    defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
-    # Seems to be no good way to set sql_mode with CLI
+class DatabaseClient(BaseDatabaseClient):
+    def runshell(self):
+        args = ['']
+        db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
+        user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
+        passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
+        host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
+        port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
+        defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
+        # Seems to be no good way to set sql_mode with CLI
     
-    if defaults_file:
-        args += ["--defaults-file=%s" % defaults_file]
-    if user:
-        args += ["--user=%s" % user]
-    if passwd:
-        args += ["--password=%s" % passwd]
-    if host:
-        args += ["--host=%s" % host]
-    if port:
-        args += ["--port=%s" % port]
-    if db:
-        args += [db]
+        if defaults_file:
+            args += ["--defaults-file=%s" % defaults_file]
+        if user:
+            args += ["--user=%s" % user]
+        if passwd:
+            args += ["--password=%s" % passwd]
+        if host:
+            args += ["--host=%s" % host]
+        if port:
+            args += ["--port=%s" % port]
+        if db:
+            args += [db]
 
-    os.execvp('mysql', args)
+        os.execvp('mysql', args)
Index: django/db/backends/mysql/introspection.py
===================================================================
--- django/db/backends/mysql/introspection.py	(revision 8156)
+++ django/db/backends/mysql/introspection.py	(working copy)
@@ -1,96 +1,100 @@
-from django.db.backends.mysql.base import DatabaseOperations
+from django.db.backends import BaseDatabaseIntrospection
 from MySQLdb import ProgrammingError, OperationalError
 from MySQLdb.constants import FIELD_TYPE
 import re
 
-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 DatabaseIntrospection(BaseDatabaseIntrospection):
+    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 __init__(self, ops):
+        self.ops = ops
+        
+    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" % self.ops.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" % self.ops.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" % self.ops.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: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(revision 8156)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -1,28 +1,31 @@
-# 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(%(max_length)s)',
-    'FilePathField':     'varchar(%(max_length)s)',
-    'FloatField':        'double precision',
-    '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 import BaseDatabaseCreation
+
+class DatabaseCreation(BaseDatabaseCreation):
+    # 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(%(max_length)s)',
+        'FilePathField':     'varchar(%(max_length)s)',
+        'FloatField':        'double precision',
+        '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: django/db/backends/oracle/base.py
===================================================================
--- django/db/backends/oracle/base.py	(revision 8156)
+++ django/db/backends/oracle/base.py	(working copy)
@@ -10,6 +10,9 @@
 
 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
 from django.db.backends.oracle import query
+from django.db.backends.oracle.client import DatabaseClient
+from django.db.backends.oracle.creation import DatabaseCreation
+from django.db.backends.oracle.introspection import DatabaseIntrospection
 from django.utils.encoding import smart_str, force_unicode
 
 # Oracle takes client-side character set encoding from the environment.
@@ -198,6 +201,10 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     features = DatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection(ops)
+    
     operators = {
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
Index: django/db/backends/oracle/client.py
===================================================================
--- django/db/backends/oracle/client.py	(revision 8156)
+++ django/db/backends/oracle/client.py	(working copy)
@@ -1,11 +1,13 @@
+from django.db.backends import BaseDatabaseClient
 from django.conf import settings
 import os
 
-def runshell():
-    dsn = settings.DATABASE_USER
-    if settings.DATABASE_PASSWORD:
-        dsn += "/%s" % settings.DATABASE_PASSWORD
-    if settings.DATABASE_NAME:
-        dsn += "@%s" % settings.DATABASE_NAME
-    args = ["sqlplus", "-L", dsn]
-    os.execvp("sqlplus", args)
+class DatabaseClient(BaseDatabaseClient):
+    def runshell(self):
+        dsn = settings.DATABASE_USER
+        if settings.DATABASE_PASSWORD:
+            dsn += "/%s" % settings.DATABASE_PASSWORD
+        if settings.DATABASE_NAME:
+            dsn += "@%s" % settings.DATABASE_NAME
+        args = ["sqlplus", "-L", dsn]
+        os.execvp("sqlplus", args)
Index: django/db/backends/oracle/introspection.py
===================================================================
--- django/db/backends/oracle/introspection.py	(revision 8156)
+++ django/db/backends/oracle/introspection.py	(working copy)
@@ -1,98 +1,102 @@
-from django.db.backends.oracle.base import DatabaseOperations
+from django.db.backends import BaseDatabaseIntrospection
+import cx_Oracle
 import re
-import cx_Oracle
 
-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()]
+class DatabaseIntrospection(BaseDatabaseIntrospection):
+    # 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',
+    }
 
-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 __init__(self, ops):
+        self.ops = ops
+        
+    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 _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 WHERE ROWNUM < 2" % self.ops.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 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
-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])
+    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))])
 
-    relations = {}
-    for row in cursor.fetchall():
-        relations[row[0]] = (row[2], row[1])
-    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 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
+    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])
 
-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 = """
-WITH primarycols AS (
- SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
- FROM   user_cons_columns, user_constraints
- WHERE  user_cons_columns.constraint_name = user_constraints.constraint_name AND
-        user_constraints.constraint_type = 'P' AND
-        user_cons_columns.table_name = %s),
- uniquecols AS (
- SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
- FROM   user_indexes, user_ind_columns
- WHERE  uniqueness = 'UNIQUE' AND
-        user_indexes.index_name = user_ind_columns.index_name AND
-        user_ind_columns.table_name = %s)
-SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
-FROM   (SELECT column_name FROM primarycols UNION SELECT column_name FROM
-uniquecols) allcols,
-      primarycols, uniquecols
-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
+        relations = {}
+        for row in cursor.fetchall():
+            relations[row[0]] = (row[2], row[1])
+        return relations
 
-# 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',
-}
+    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
+     WHERE  user_cons_columns.constraint_name = user_constraints.constraint_name AND
+            user_constraints.constraint_type = 'P' AND
+            user_cons_columns.table_name = %s),
+     uniquecols AS (
+     SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
+     FROM   user_indexes, user_ind_columns
+     WHERE  uniqueness = 'UNIQUE' AND
+            user_indexes.index_name = user_ind_columns.index_name AND
+            user_ind_columns.table_name = %s)
+    SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
+    FROM   (SELECT column_name FROM primarycols UNION SELECT column_name FROM
+    uniquecols) allcols,
+          primarycols, uniquecols
+    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
+
Index: django/db/backends/oracle/creation.py
===================================================================
--- django/db/backends/oracle/creation.py	(revision 8156)
+++ django/db/backends/oracle/creation.py	(working copy)
@@ -1,291 +1,292 @@
 import sys, time
 from django.core import management
+from django.db.backends import BaseDatabaseCreation
 
-# 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.
-#
-# Any format strings starting with "qn_" are quoted before being used in the
-# output (the "qn_" prefix is stripped before the lookup is performed.
-
-DATA_TYPES = {
-    'AutoField':                    'NUMBER(11)',
-    'BooleanField':                 'NUMBER(1) CHECK (%(qn_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(%(max_length)s)',
-    'FilePathField':                'NVARCHAR2(%(max_length)s)',
-    'FloatField':                   'DOUBLE PRECISION',
-    'IntegerField':                 'NUMBER(11)',
-    'IPAddressField':               'VARCHAR2(15)',
-    'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
-    'OneToOneField':                'NUMBER(11)',
-    'PhoneNumberField':             'VARCHAR2(20)',
-    'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
-    'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(qn_column)s >= 0)',
-    'SlugField':                    'NVARCHAR2(50)',
-    'SmallIntegerField':            'NUMBER(11)',
-    'TextField':                    'NCLOB',
-    'TimeField':                    'TIMESTAMP',
-    'URLField':                     'VARCHAR2(%(max_length)s)',
-    '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)
+class DatabaseCreation(BaseDatabaseCreation):
+    # 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.
+    #
+    # Any format strings starting with "qn_" are quoted before being used in the
+    # output (the "qn_" prefix is stripped before the lookup is performed.
 
-    parameters = {
-        'dbname': TEST_DATABASE_NAME,
-        'user': TEST_DATABASE_USER,
-        'password': TEST_DATABASE_PASSWD,
-        'tblspace': TEST_DATABASE_TBLSPACE,
-        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
- 	}
+    data_types = {
+        'AutoField':                    'NUMBER(11)',
+        'BooleanField':                 'NUMBER(1) CHECK (%(qn_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(%(max_length)s)',
+        'FilePathField':                'NVARCHAR2(%(max_length)s)',
+        'FloatField':                   'DOUBLE PRECISION',
+        'IntegerField':                 'NUMBER(11)',
+        'IPAddressField':               'VARCHAR2(15)',
+        'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
+        'OneToOneField':                'NUMBER(11)',
+        'PhoneNumberField':             'VARCHAR2(20)',
+        'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
+        'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(qn_column)s >= 0)',
+        'SlugField':                    'NVARCHAR2(50)',
+        'SmallIntegerField':            'NUMBER(11)',
+        'TextField':                    'NCLOB',
+        'TimeField':                    'TIMESTAMP',
+        'URLField':                     'VARCHAR2(%(max_length)s)',
+        'USStateField':                 'CHAR(2)',
+    }
+    
+    def create_test_db(settings, connection, verbosity=1, autoclobber=False):
+        TEST_DATABASE_NAME = self._test_database_name(settings)
+        TEST_DATABASE_USER = self._test_database_user(settings)
+        TEST_DATABASE_PASSWD = self._test_database_passwd(settings)
+        TEST_DATABASE_TBLSPACE = self._test_database_tblspace(settings)
+        TEST_DATABASE_TBLSPACE_TMP = self._test_database_tblspace_tmp(settings)
 
-    REMEMBER['user'] = settings.DATABASE_USER
-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
+        parameters = {
+            'dbname': TEST_DATABASE_NAME,
+            'user': TEST_DATABASE_USER,
+            'password': TEST_DATABASE_PASSWD,
+            'tblspace': TEST_DATABASE_TBLSPACE,
+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
+     	}
 
-    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)
+        self.remember['user'] = settings.DATABASE_USER
+        self.remember['passwd'] = settings.DATABASE_PASSWORD
 
-    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)
+        cursor = connection.cursor()
+        if self._test_database_create(settings):
+            if verbosity >= 1:
+                print 'Creating test database...'
+            try:
+                self._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..."
+                        self._destroy_test_db(cursor, parameters, verbosity)
+                        if verbosity >= 1:
+                            print "Creating test database..."
+                        self._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)
 
-    connection.close()
-    settings.DATABASE_USER = TEST_DATABASE_USER
-    settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
+        if self._test_user_create(settings):
+            if verbosity >= 1:
+                print "Creating test user..."
+            try:
+                self._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..."
+                        self._destroy_test_user(cursor, parameters, verbosity)
+                        if verbosity >= 1:
+                            print "Creating test user..."
+                        self._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)
 
-    management.call_command('syncdb', verbosity=verbosity, interactive=False)
+        connection.close()
+        settings.DATABASE_USER = TEST_DATABASE_USER
+        settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
 
-    # Get a cursor (even though we don't need one yet). This has
-    # the side effect of initializing the test database.
-    cursor = connection.cursor()
+        management.call_command('syncdb', verbosity=verbosity, interactive=False)
 
-def destroy_test_db(settings, connection, old_database_name, verbosity=1):
-    connection.close()
+        # Get a cursor (even though we don't need one yet). This has
+        # the side effect of initializing the test database.
+        cursor = connection.cursor()
 
-    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 destroy_test_db(settings, connection, old_database_name, verbosity=1):
+        connection.close()
 
-    settings.DATABASE_NAME = old_database_name
-    settings.DATABASE_USER = REMEMBER['user']
-    settings.DATABASE_PASSWORD = REMEMBER['passwd']
+        TEST_DATABASE_NAME = self._test_database_name(settings)
+        TEST_DATABASE_USER = self._test_database_user(settings)
+        TEST_DATABASE_PASSWD = self._test_database_passwd(settings)
+        TEST_DATABASE_TBLSPACE = self._test_database_tblspace(settings)
+        TEST_DATABASE_TBLSPACE_TMP = self._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,
- 	}
+        settings.DATABASE_NAME = old_database_name
+        settings.DATABASE_USER = self.remember['user']
+        settings.DATABASE_PASSWORD = self.remember['passwd']
 
-    REMEMBER['user'] = settings.DATABASE_USER
-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
+        parameters = {
+            'dbname': TEST_DATABASE_NAME,
+            'user': TEST_DATABASE_USER,
+            'password': TEST_DATABASE_PASSWD,
+            'tblspace': TEST_DATABASE_TBLSPACE,
+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
+     	}
 
-    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()
+        self.remember['user'] = settings.DATABASE_USER
+        self.remember['passwd'] = settings.DATABASE_PASSWORD
 
-def _create_test_db(cursor, parameters, verbosity):
-    if verbosity >= 2:
-        print "_create_test_db(): dbname = %s" % parameters['dbname']
-    statements = [
-        """CREATE TABLESPACE %(tblspace)s
-           DATAFILE '%(tblspace)s.dbf' SIZE 20M
-           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
-        """,
-        """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
-           TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
-           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
-        """,
-    ]
-    _execute_statements(cursor, statements, parameters, verbosity)
+        cursor = connection.cursor()
+        time.sleep(1) # To avoid "database is being accessed by other users" errors.
+        if self._test_user_create(settings):
+            if verbosity >= 1:
+                print 'Destroying test user...'
+            self._destroy_test_user(cursor, parameters, verbosity)
+        if self._test_database_create(settings):
+            if verbosity >= 1:
+                print 'Destroying test database...'
+            self._destroy_test_db(cursor, parameters, verbosity)
+        connection.close()
 
-def _create_test_user(cursor, parameters, verbosity):
-    if verbosity >= 2:
-        print "_create_test_user(): username = %s" % parameters['user']
-    statements = [
-        """CREATE USER %(user)s
-           IDENTIFIED BY %(password)s
-           DEFAULT TABLESPACE %(tblspace)s
-           TEMPORARY TABLESPACE %(tblspace_temp)s
-        """,
-        """GRANT CONNECT, RESOURCE TO %(user)s""",
-    ]
-    _execute_statements(cursor, statements, parameters, verbosity)
+    def _create_test_db(cursor, parameters, verbosity):
+        if verbosity >= 2:
+            print "_create_test_db(): dbname = %s" % parameters['dbname']
+        statements = [
+            """CREATE TABLESPACE %(tblspace)s
+               DATAFILE '%(tblspace)s.dbf' SIZE 20M
+               REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
+            """,
+            """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
+               TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
+               REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
+            """,
+        ]
+        _execute_statements(cursor, statements, parameters, verbosity)
 
-def _destroy_test_db(cursor, parameters, verbosity):
-    if verbosity >= 2:
-        print "_destroy_test_db(): dbname=%s" % parameters['dbname']
-    statements = [
-        'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
-        'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
+    def _create_test_user(cursor, parameters, verbosity):
+        if verbosity >= 2:
+            print "_create_test_user(): username = %s" % parameters['user']
+        statements = [
+            """CREATE USER %(user)s
+               IDENTIFIED BY %(password)s
+               DEFAULT TABLESPACE %(tblspace)s
+               TEMPORARY TABLESPACE %(tblspace_temp)s
+            """,
+            """GRANT CONNECT, RESOURCE TO %(user)s""",
         ]
-    _execute_statements(cursor, statements, parameters, verbosity)
+        _execute_statements(cursor, statements, parameters, verbosity)
 
-def _destroy_test_user(cursor, parameters, verbosity):
-    if verbosity >= 2:
-        print "_destroy_test_user(): user=%s" % parameters['user']
-        print "Be patient.  This can take some time..."
-    statements = [
-        'DROP USER %(user)s CASCADE',
-    ]
-    _execute_statements(cursor, statements, parameters, verbosity)
+    def _destroy_test_db(cursor, parameters, verbosity):
+        if verbosity >= 2:
+            print "_destroy_test_db(): dbname=%s" % parameters['dbname']
+        statements = [
+            'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
+            'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
+            ]
+        _execute_statements(cursor, statements, parameters, verbosity)
 
-def _execute_statements(cursor, statements, parameters, verbosity):
-    for template in statements:
-        stmt = template % parameters
+    def _destroy_test_user(cursor, parameters, verbosity):
         if verbosity >= 2:
-            print stmt
+            print "_destroy_test_user(): user=%s" % parameters['user']
+            print "Be patient.  This can take some time..."
+        statements = [
+            'DROP USER %(user)s CASCADE',
+        ]
+        _execute_statements(cursor, statements, parameters, verbosity)
+
+    def _execute_statements(cursor, statements, parameters, verbosity):
+        for template in statements:
+            stmt = template % parameters
+            if verbosity >= 2:
+                print stmt
+            try:
+                cursor.execute(stmt)
+            except Exception, err:
+                sys.stderr.write("Failed (%s)\n" % (err))
+                raise
+
+    def _test_database_name(settings):
+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
         try:
-            cursor.execute(stmt)
-        except Exception, err:
-            sys.stderr.write("Failed (%s)\n" % (err))
+            if settings.TEST_DATABASE_NAME:
+                name = settings.TEST_DATABASE_NAME
+        except AttributeError:
+            pass
+        except:
             raise
+        return name
 
-def _test_database_name(settings):
-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
-    try:
-        if settings.TEST_DATABASE_NAME:
-            name = settings.TEST_DATABASE_NAME
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_database_create(settings):
+        name = True
+        try:
+            if settings.TEST_DATABASE_CREATE:
+                name = True
+            else:
+                name = False
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
 
-def _test_database_create(settings):
-    name = True
-    try:
-        if settings.TEST_DATABASE_CREATE:
-            name = True
-        else:
-            name = False
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_user_create(settings):
+        name = True
+        try:
+            if settings.TEST_USER_CREATE:
+                name = True
+            else:
+                name = False
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
 
-def _test_user_create(settings):
-    name = True
-    try:
-        if settings.TEST_USER_CREATE:
-            name = True
-        else:
-            name = False
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_database_user(settings):
+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
+        try:
+            if settings.TEST_DATABASE_USER:
+                name = settings.TEST_DATABASE_USER
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
 
-def _test_database_user(settings):
-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
-    try:
-        if settings.TEST_DATABASE_USER:
-            name = settings.TEST_DATABASE_USER
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_database_passwd(settings):
+        name = PASSWORD
+        try:
+            if settings.TEST_DATABASE_PASSWD:
+                name = settings.TEST_DATABASE_PASSWD
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
 
-def _test_database_passwd(settings):
-    name = PASSWORD
-    try:
-        if settings.TEST_DATABASE_PASSWD:
-            name = settings.TEST_DATABASE_PASSWD
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_database_tblspace(settings):
+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
+        try:
+            if settings.TEST_DATABASE_TBLSPACE:
+                name = settings.TEST_DATABASE_TBLSPACE
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
 
-def _test_database_tblspace(settings):
-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
-    try:
-        if settings.TEST_DATABASE_TBLSPACE:
-            name = settings.TEST_DATABASE_TBLSPACE
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
-
-def _test_database_tblspace_tmp(settings):
-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
-    try:
-        if settings.TEST_DATABASE_TBLSPACE_TMP:
-            name = settings.TEST_DATABASE_TBLSPACE_TMP
-    except AttributeError:
-        pass
-    except:
-        raise
-    return name
+    def _test_database_tblspace_tmp(settings):
+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
+        try:
+            if settings.TEST_DATABASE_TBLSPACE_TMP:
+                name = settings.TEST_DATABASE_TBLSPACE_TMP
+        except AttributeError:
+            pass
+        except:
+            raise
+        return name
Index: django/db/backends/__init__.py
===================================================================
--- django/db/backends/__init__.py	(revision 8156)
+++ django/db/backends/__init__.py	(working copy)
@@ -325,3 +325,25 @@
         """
         return self.year_lookup_bounds(value)
 
+class BaseDatabaseCreation(object):
+    """
+    This class encapsulates all backend-specific differences that pertain to
+    database *creation*, such as the column types to use for particular Django
+    Fields.
+    """
+    data_types = {}
+
+class BaseDatabaseIntrospection(object):
+    """
+    This class encapsulates all backend-specific introspection utilities
+    """
+    data_types_reverse = {}
+
+class BaseDatabaseClient(object):
+    """
+    This class encapsualtes all backend-specific methods for opening a 
+    client shell
+    """
+    def runshell():
+	    raise NotImplementedError()
+        
\ No newline at end of file
Index: django/db/backends/postgresql_psycopg2/base.py
===================================================================
--- django/db/backends/postgresql_psycopg2/base.py	(revision 8156)
+++ django/db/backends/postgresql_psycopg2/base.py	(working copy)
@@ -6,6 +6,10 @@
 
 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures
 from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
+from django.db.backends.postgresql.client import DatabaseClient
+from django.db.backends.postgresql.creation import DatabaseCreation
+from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
+
 from django.utils.safestring import SafeUnicode
 try:
     import psycopg2 as Database
@@ -33,6 +37,10 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     features = DatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection(ops)
+
     operators = {
         'exact': '= %s',
         'iexact': 'ILIKE %s',
Index: django/db/backends/postgresql_psycopg2/client.py
===================================================================
--- django/db/backends/postgresql_psycopg2/client.py	(revision 8156)
+++ django/db/backends/postgresql_psycopg2/client.py	(working copy)
@@ -1 +0,0 @@
-from django.db.backends.postgresql.client import *
Index: django/db/backends/postgresql_psycopg2/introspection.py
===================================================================
--- django/db/backends/postgresql_psycopg2/introspection.py	(revision 8156)
+++ django/db/backends/postgresql_psycopg2/introspection.py	(working copy)
@@ -1,83 +1,21 @@
-from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
+from django.db.backends.postgresql.introspection import DatabaseIntrospection as PostgresDatabaseIntrospection
 
-quote_name = DatabaseOperations().quote_name
+class DatabaseIntrospection(PostgresDatabaseIntrospection):
 
-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()]
-
-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_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_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
-
-# 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_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
Index: django/db/backends/postgresql_psycopg2/creation.py
===================================================================
--- django/db/backends/postgresql_psycopg2/creation.py	(revision 8156)
+++ django/db/backends/postgresql_psycopg2/creation.py	(working copy)
@@ -1 +0,0 @@
-from django.db.backends.postgresql.creation import *
Index: django/db/backends/dummy/base.py
===================================================================
--- django/db/backends/dummy/base.py	(revision 8156)
+++ django/db/backends/dummy/base.py	(working copy)
@@ -9,6 +9,7 @@
 
 from django.core.exceptions import ImproperlyConfigured
 from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations
+from django.db.backends import BaseDatabaseClient, BaseDatabaseCreation, BaseDatabaseIntrospection
 
 def complain(*args, **kwargs):
     raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
@@ -25,9 +26,25 @@
 class DatabaseOperations(BaseDatabaseOperations):
     quote_name = complain
 
+class DatabaseClient(BaseDatabaseClient):
+    runshell = complain
+    
+class DatabaseCreation(BaseDatabaseCreation):
+    pass
+    
+class DatabaseIntrospection(BaseDatabaseIntrospection):
+    get_table_list = complain
+    get_table_description = complain
+    get_relations = complain
+    get_indexes = complain
+    
 class DatabaseWrapper(object):
     features = BaseDatabaseFeatures()
     ops = DatabaseOperations()
+    client = DatabaseClient()
+    creation = DatabaseCreation()
+    introspection = DatabaseIntrospection()
+
     operators = {}
     cursor = complain
     _commit = complain
Index: django/db/backends/dummy/client.py
===================================================================
--- django/db/backends/dummy/client.py	(revision 8156)
+++ django/db/backends/dummy/client.py	(working copy)
@@ -1,3 +0,0 @@
-from django.db.backends.dummy.base import complain
-
-runshell = complain
Index: django/db/backends/dummy/introspection.py
===================================================================
--- django/db/backends/dummy/introspection.py	(revision 8156)
+++ django/db/backends/dummy/introspection.py	(working copy)
@@ -1,8 +0,0 @@
-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 = {}
Index: django/db/backends/dummy/creation.py
===================================================================
--- django/db/backends/dummy/creation.py	(revision 8156)
+++ django/db/backends/dummy/creation.py	(working copy)
@@ -1 +0,0 @@
-DATA_TYPES = {}
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 8156)
+++ django/db/backends/creation.py	(working copy)
@@ -1,7 +0,0 @@
-class BaseCreation(object):
-    """
-    This class encapsulates all backend-specific differences that pertain to
-    database *creation*, such as the column types to use for particular Django
-    Fields.
-    """
-    pass
Index: django/core/management/commands/inspectdb.py
===================================================================
--- django/core/management/commands/inspectdb.py	(revision 8156)
+++ django/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,7 +63,7 @@
                         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.')
Index: django/core/management/commands/dbshell.py
===================================================================
--- django/core/management/commands/dbshell.py	(revision 8156)
+++ django/core/management/commands/dbshell.py	(working copy)
@@ -6,5 +6,5 @@
     requires_model_validation = False
 
     def handle_noargs(self, **options):
-        from django.db import runshell
-        runshell()
+        from django.db import connection
+        connection.client.runshell()
Index: django/core/management/sql.py
===================================================================
--- django/core/management/sql.py	(revision 8157)
+++ django/core/management/sql.py	(working copy)
@@ -9,9 +9,9 @@
 
 def table_names():
     "Returns a list of all table names that exist in the database."
-    from django.db import connection, get_introspection_module
+    from django.db import connection
     cursor = connection.cursor()
-    return set(get_introspection_module().get_table_list(cursor))
+    return set(connection.introspection.get_table_list(cursor))
 
 def django_table_names(only_existing=False):
     """
