Code

Ticket #5461: 5461-r8156.diff

File 5461-r8156.diff, 90.3 KB (added by russellm, 6 years ago)

Refactored Creation, Introspection and Client for backend

Line 
1Index: django/test/utils.py
2===================================================================
3--- django/test/utils.py        (revision 8156)
4+++ django/test/utils.py        (working copy)
5@@ -1,6 +1,6 @@
6 import sys, time, os
7 from django.conf import settings
8-from django.db import connection, get_creation_module
9+from django.db import connection
10 from django.core import mail
11 from django.core.management import call_command
12 from django.dispatch import dispatcher
13@@ -101,9 +101,8 @@
14     database already exists. Returns the name of the test database created.
15     """
16     # If the database backend wants to create the test DB itself, let it
17-    creation_module = get_creation_module()
18-    if hasattr(creation_module, "create_test_db"):
19-        creation_module.create_test_db(settings, connection, verbosity, autoclobber)
20+    if hasattr(connection.creation, "create_test_db"):
21+        connection.creation.create_test_db(settings, connection, verbosity, autoclobber)
22         return
23 
24     if verbosity >= 1:
25@@ -191,9 +190,8 @@
26 
27 def destroy_test_db(old_database_name, verbosity=1):
28     # If the database wants to drop the test DB itself, let it
29-    creation_module = get_creation_module()
30-    if hasattr(creation_module, "destroy_test_db"):
31-        creation_module.destroy_test_db(settings, connection, old_database_name, verbosity)
32+    if hasattr(connection.creation, "destroy_test_db"):
33+        connection.creation.destroy_test_db(settings, connection, old_database_name, verbosity)
34         return
35 
36     if verbosity >= 1:
37Index: django/db/models/fields/__init__.py
38===================================================================
39--- django/db/models/fields/__init__.py (revision 8156)
40+++ django/db/models/fields/__init__.py (working copy)
41@@ -7,7 +7,7 @@
42 except ImportError:
43     from django.utils import _decimal as decimal    # for Python 2.3
44 
45-from django.db import connection, get_creation_module
46+from django.db import connection
47 from django.db.models import signals
48 from django.db.models.query_utils import QueryWrapper
49 from django.dispatch import dispatcher
50@@ -150,14 +150,14 @@
51         # as the TextField Django field type, which means XMLField's
52         # get_internal_type() returns 'TextField'.
53         #
54-        # But the limitation of the get_internal_type() / DATA_TYPES approach
55+        # But the limitation of the get_internal_type() / data_types approach
56         # is that it cannot handle database column types that aren't already
57         # mapped to one of the built-in Django field types. In this case, you
58         # can implement db_type() instead of get_internal_type() to specify
59         # exactly which wacky database column type you want to use.
60         data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
61         try:
62-            return get_creation_module().DATA_TYPES[self.get_internal_type()] % data
63+            return connection.creation.data_types[self.get_internal_type()] % data
64         except KeyError:
65             return None
66 
67Index: django/db/__init__.py
68===================================================================
69--- django/db/__init__.py       (revision 8156)
70+++ django/db/__init__.py       (working copy)
71@@ -15,14 +15,14 @@
72     # backends that ships with Django, so look there first.
73     _import_path = 'django.db.backends.'
74     backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
75-    creation = __import__('%s%s.creation' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
76 except ImportError, e:
77+    import traceback
78+    traceback.print_exc()
79     # If the import failed, we might be looking for a database backend
80     # distributed external to Django. So we'll try that next.
81     try:
82         _import_path = ''
83         backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
84-        creation = __import__('%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
85     except ImportError, e_user:
86         # The database backend wasn't found. Display a helpful error message
87         # listing all possible (built-in) database backends.
88@@ -35,22 +35,6 @@
89         else:
90             raise # If there's some other error, this must be an error in Django itself.
91 
92-def _import_database_module(import_path='', module_name=''):
93-    """Lazily import a database module when requested."""
94-    return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
95-
96-# We don't want to import the introspect module unless someone asks for it, so
97-# lazily load it on demmand.
98-get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
99-
100-def get_creation_module():
101-    return creation
102-
103-# We want runshell() to work the same way, but we have to treat it a
104-# little differently (since it just runs instead of returning a module like
105-# the above) and wrap the lazily-loaded runshell() method.
106-runshell = lambda: _import_database_module(_import_path, "client").runshell()
107-
108 # Convenient aliases for backend bits.
109 connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
110 DatabaseError = backend.DatabaseError
111Index: django/db/backends/postgresql/base.py
112===================================================================
113--- django/db/backends/postgresql/base.py       (revision 8156)
114+++ django/db/backends/postgresql/base.py       (working copy)
115@@ -7,6 +7,10 @@
116 from django.utils.encoding import smart_str, smart_unicode
117 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util
118 from django.db.backends.postgresql.operations import DatabaseOperations
119+from django.db.backends.postgresql.client import DatabaseClient
120+from django.db.backends.postgresql.creation import DatabaseCreation
121+from django.db.backends.postgresql.introspection import DatabaseIntrospection
122+
123 try:
124     import psycopg as Database
125 except ImportError, e:
126@@ -65,6 +69,10 @@
127 class DatabaseWrapper(BaseDatabaseWrapper):
128     features = DatabaseFeatures()
129     ops = DatabaseOperations()
130+    client = DatabaseClient()
131+    creation = DatabaseCreation()
132+    introspection = DatabaseIntrospection(ops)
133+
134     operators = {
135         'exact': '= %s',
136         'iexact': 'ILIKE %s',
137Index: django/db/backends/postgresql/client.py
138===================================================================
139--- django/db/backends/postgresql/client.py     (revision 8156)
140+++ django/db/backends/postgresql/client.py     (working copy)
141@@ -1,15 +1,17 @@
142+from django.db.backends import BaseDatabaseClient
143 from django.conf import settings
144 import os
145 
146-def runshell():
147-    args = ['psql']
148-    if settings.DATABASE_USER:
149-        args += ["-U", settings.DATABASE_USER]
150-    if settings.DATABASE_PASSWORD:
151-        args += ["-W"]
152-    if settings.DATABASE_HOST:
153-        args.extend(["-h", settings.DATABASE_HOST])
154-    if settings.DATABASE_PORT:
155-        args.extend(["-p", str(settings.DATABASE_PORT)])
156-    args += [settings.DATABASE_NAME]
157-    os.execvp('psql', args)
158+class DatabaseClient(BaseDatabaseClient):
159+    def runshell(self):
160+        args = ['psql']
161+        if settings.DATABASE_USER:
162+            args += ["-U", settings.DATABASE_USER]
163+        if settings.DATABASE_PASSWORD:
164+            args += ["-W"]
165+        if settings.DATABASE_HOST:
166+            args.extend(["-h", settings.DATABASE_HOST])
167+        if settings.DATABASE_PORT:
168+            args.extend(["-p", str(settings.DATABASE_PORT)])
169+        args += [settings.DATABASE_NAME]
170+        os.execvp('psql', args)
171Index: django/db/backends/postgresql/introspection.py
172===================================================================
173--- django/db/backends/postgresql/introspection.py      (revision 8156)
174+++ django/db/backends/postgresql/introspection.py      (working copy)
175@@ -1,86 +1,89 @@
176-from django.db.backends.postgresql.base import DatabaseOperations
177+from django.db.backends import BaseDatabaseIntrospection
178 
179-quote_name = DatabaseOperations().quote_name
180+class DatabaseIntrospection(BaseDatabaseIntrospection):
181+    # Maps type codes to Django Field types.
182+    data_types_reverse = {
183+        16: 'BooleanField',
184+        21: 'SmallIntegerField',
185+        23: 'IntegerField',
186+        25: 'TextField',
187+        701: 'FloatField',
188+        869: 'IPAddressField',
189+        1043: 'CharField',
190+        1082: 'DateField',
191+        1083: 'TimeField',
192+        1114: 'DateTimeField',
193+        1184: 'DateTimeField',
194+        1266: 'TimeField',
195+        1700: 'DecimalField',
196+    }
197+   
198+    def __init__(self, ops):
199+        self.ops = ops
200+       
201+    def get_table_list(self, cursor):
202+        "Returns a list of table names in the current database."
203+        cursor.execute("""
204+            SELECT c.relname
205+            FROM pg_catalog.pg_class c
206+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
207+            WHERE c.relkind IN ('r', 'v', '')
208+                AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
209+                AND pg_catalog.pg_table_is_visible(c.oid)""")
210+        return [row[0] for row in cursor.fetchall()]
211 
212-def get_table_list(cursor):
213-    "Returns a list of table names in the current database."
214-    cursor.execute("""
215-        SELECT c.relname
216-        FROM pg_catalog.pg_class c
217-        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
218-        WHERE c.relkind IN ('r', 'v', '')
219-            AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
220-            AND pg_catalog.pg_table_is_visible(c.oid)""")
221-    return [row[0] for row in cursor.fetchall()]
222+    def get_table_description(self, cursor, table_name):
223+        "Returns a description of the table, with the DB-API cursor.description interface."
224+        cursor.execute("SELECT * FROM %s LIMIT 1" % self.ops.quote_name(table_name))
225+        return cursor.description
226 
227-def get_table_description(cursor, table_name):
228-    "Returns a description of the table, with the DB-API cursor.description interface."
229-    cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
230-    return cursor.description
231+    def get_relations(self, cursor, table_name):
232+        """
233+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
234+        representing all relationships to the given table. Indexes are 0-based.
235+        """
236+        cursor.execute("""
237+            SELECT con.conkey, con.confkey, c2.relname
238+            FROM pg_constraint con, pg_class c1, pg_class c2
239+            WHERE c1.oid = con.conrelid
240+                AND c2.oid = con.confrelid
241+                AND c1.relname = %s
242+                AND con.contype = 'f'""", [table_name])
243+        relations = {}
244+        for row in cursor.fetchall():
245+            try:
246+                # row[0] and row[1] are like "{2}", so strip the curly braces.
247+                relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
248+            except ValueError:
249+                continue
250+        return relations
251 
252-def get_relations(cursor, table_name):
253-    """
254-    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
255-    representing all relationships to the given table. Indexes are 0-based.
256-    """
257-    cursor.execute("""
258-        SELECT con.conkey, con.confkey, c2.relname
259-        FROM pg_constraint con, pg_class c1, pg_class c2
260-        WHERE c1.oid = con.conrelid
261-            AND c2.oid = con.confrelid
262-            AND c1.relname = %s
263-            AND con.contype = 'f'""", [table_name])
264-    relations = {}
265-    for row in cursor.fetchall():
266-        try:
267-            # row[0] and row[1] are like "{2}", so strip the curly braces.
268-            relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
269-        except ValueError:
270-            continue
271-    return relations
272+    def get_indexes(self, cursor, table_name):
273+        """
274+        Returns a dictionary of fieldname -> infodict for the given table,
275+        where each infodict is in the format:
276+            {'primary_key': boolean representing whether it's the primary key,
277+             'unique': boolean representing whether it's a unique index}
278+        """
279+        # This query retrieves each index on the given table, including the
280+        # first associated field name
281+        cursor.execute("""
282+            SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
283+            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
284+                pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
285+            WHERE c.oid = idx.indrelid
286+                AND idx.indexrelid = c2.oid
287+                AND attr.attrelid = c.oid
288+                AND attr.attnum = idx.indkey[0]
289+                AND c.relname = %s""", [table_name])
290+        indexes = {}
291+        for row in cursor.fetchall():
292+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
293+            # a string of space-separated integers. This designates the field
294+            # indexes (1-based) of the fields that have indexes on the table.
295+            # Here, we skip any indexes across multiple fields.
296+            if ' ' in row[1]:
297+                continue
298+            indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
299+        return indexes
300 
301-def get_indexes(cursor, table_name):
302-    """
303-    Returns a dictionary of fieldname -> infodict for the given table,
304-    where each infodict is in the format:
305-        {'primary_key': boolean representing whether it's the primary key,
306-         'unique': boolean representing whether it's a unique index}
307-    """
308-    # This query retrieves each index on the given table, including the
309-    # first associated field name
310-    cursor.execute("""
311-        SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
312-        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
313-            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
314-        WHERE c.oid = idx.indrelid
315-            AND idx.indexrelid = c2.oid
316-            AND attr.attrelid = c.oid
317-            AND attr.attnum = idx.indkey[0]
318-            AND c.relname = %s""", [table_name])
319-    indexes = {}
320-    for row in cursor.fetchall():
321-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
322-        # a string of space-separated integers. This designates the field
323-        # indexes (1-based) of the fields that have indexes on the table.
324-        # Here, we skip any indexes across multiple fields.
325-        if ' ' in row[1]:
326-            continue
327-        indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
328-    return indexes
329-
330-# Maps type codes to Django Field types.
331-DATA_TYPES_REVERSE = {
332-    16: 'BooleanField',
333-    21: 'SmallIntegerField',
334-    23: 'IntegerField',
335-    25: 'TextField',
336-    701: 'FloatField',
337-    869: 'IPAddressField',
338-    1043: 'CharField',
339-    1082: 'DateField',
340-    1083: 'TimeField',
341-    1114: 'DateTimeField',
342-    1184: 'DateTimeField',
343-    1266: 'TimeField',
344-    1700: 'DecimalField',
345-}
346Index: django/db/backends/postgresql/creation.py
347===================================================================
348--- django/db/backends/postgresql/creation.py   (revision 8156)
349+++ django/db/backends/postgresql/creation.py   (working copy)
350@@ -1,28 +1,31 @@
351-# This dictionary maps Field objects to their associated PostgreSQL column
352-# types, as strings. Column-type strings can contain format strings; they'll
353-# be interpolated against the values of Field.__dict__ before being output.
354-# If a column type is set to None, it won't be included in the output.
355-DATA_TYPES = {
356-    'AutoField':         'serial',
357-    'BooleanField':      'boolean',
358-    'CharField':         'varchar(%(max_length)s)',
359-    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
360-    'DateField':         'date',
361-    'DateTimeField':     'timestamp with time zone',
362-    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
363-    'FileField':         'varchar(%(max_length)s)',
364-    'FilePathField':     'varchar(%(max_length)s)',
365-    'FloatField':        'double precision',
366-    'IntegerField':      'integer',
367-    'IPAddressField':    'inet',
368-    'NullBooleanField':  'boolean',
369-    'OneToOneField':     'integer',
370-    'PhoneNumberField':  'varchar(20)',
371-    'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
372-    'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
373-    'SlugField':         'varchar(%(max_length)s)',
374-    'SmallIntegerField': 'smallint',
375-    'TextField':         'text',
376-    'TimeField':         'time',
377-    'USStateField':      'varchar(2)',
378-}
379+from django.db.backends import BaseDatabaseCreation
380+
381+class DatabaseCreation(BaseDatabaseCreation):
382+    # This dictionary maps Field objects to their associated PostgreSQL column
383+    # types, as strings. Column-type strings can contain format strings; they'll
384+    # be interpolated against the values of Field.__dict__ before being output.
385+    # If a column type is set to None, it won't be included in the output.
386+    data_types = {
387+        'AutoField':         'serial',
388+        'BooleanField':      'boolean',
389+        'CharField':         'varchar(%(max_length)s)',
390+        'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
391+        'DateField':         'date',
392+        'DateTimeField':     'timestamp with time zone',
393+        'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
394+        'FileField':         'varchar(%(max_length)s)',
395+        'FilePathField':     'varchar(%(max_length)s)',
396+        'FloatField':        'double precision',
397+        'IntegerField':      'integer',
398+        'IPAddressField':    'inet',
399+        'NullBooleanField':  'boolean',
400+        'OneToOneField':     'integer',
401+        'PhoneNumberField':  'varchar(20)',
402+        'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
403+        'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
404+        'SlugField':         'varchar(%(max_length)s)',
405+        'SmallIntegerField': 'smallint',
406+        'TextField':         'text',
407+        'TimeField':         'time',
408+        'USStateField':      'varchar(2)',
409+    }
410Index: django/db/backends/sqlite3/base.py
411===================================================================
412--- django/db/backends/sqlite3/base.py  (revision 8156)
413+++ django/db/backends/sqlite3/base.py  (working copy)
414@@ -7,6 +7,10 @@
415 """
416 
417 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
418+from django.db.backends.sqlite3.client import DatabaseClient
419+from django.db.backends.sqlite3.creation import DatabaseCreation
420+from django.db.backends.sqlite3.introspection import DatabaseIntrospection
421+
422 try:
423     try:
424         from sqlite3 import dbapi2 as Database
425@@ -93,6 +97,9 @@
426 class DatabaseWrapper(BaseDatabaseWrapper):
427     features = DatabaseFeatures()
428     ops = DatabaseOperations()
429+    client = DatabaseClient()
430+    creation = DatabaseCreation()
431+    introspection = DatabaseIntrospection(ops)
432 
433     # SQLite requires LIKE statements to include an ESCAPE clause if the value
434     # being escaped has a percent or underscore in it.
435Index: django/db/backends/sqlite3/client.py
436===================================================================
437--- django/db/backends/sqlite3/client.py        (revision 8156)
438+++ django/db/backends/sqlite3/client.py        (working copy)
439@@ -1,6 +1,8 @@
440+from django.db.backends import BaseDatabaseClient
441 from django.conf import settings
442 import os
443 
444-def runshell():
445-    args = ['', settings.DATABASE_NAME]
446-    os.execvp('sqlite3', args)
447+class DatabaseClient(BaseDatabaseClient):
448+    def runshell(self):
449+        args = ['', settings.DATABASE_NAME]
450+        os.execvp('sqlite3', args)
451Index: django/db/backends/sqlite3/introspection.py
452===================================================================
453--- django/db/backends/sqlite3/introspection.py (revision 8156)
454+++ django/db/backends/sqlite3/introspection.py (working copy)
455@@ -1,84 +1,30 @@
456-from django.db.backends.sqlite3.base import DatabaseOperations
457+from django.db.backends import BaseDatabaseIntrospection
458 
459-quote_name = DatabaseOperations().quote_name
460-
461-def get_table_list(cursor):
462-    "Returns a list of table names in the current database."
463-    # Skip the sqlite_sequence system table used for autoincrement key
464-    # generation.
465-    cursor.execute("""
466-        SELECT name FROM sqlite_master
467-        WHERE type='table' AND NOT name='sqlite_sequence'
468-        ORDER BY name""")
469-    return [row[0] for row in cursor.fetchall()]
470-
471-def get_table_description(cursor, table_name):
472-    "Returns a description of the table, with the DB-API cursor.description interface."
473-    return [(info['name'], info['type'], None, None, None, None,
474-             info['null_ok']) for info in _table_info(cursor, table_name)]
475-
476-def get_relations(cursor, table_name):
477-    raise NotImplementedError
478-
479-def get_indexes(cursor, table_name):
480-    """
481-    Returns a dictionary of fieldname -> infodict for the given table,
482-    where each infodict is in the format:
483-        {'primary_key': boolean representing whether it's the primary key,
484-         'unique': boolean representing whether it's a unique index}
485-    """
486-    indexes = {}
487-    for info in _table_info(cursor, table_name):
488-        indexes[info['name']] = {'primary_key': info['pk'] != 0,
489-                                 'unique': False}
490-    cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name))
491-    # seq, name, unique
492-    for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
493-        if not unique:
494-            continue
495-        cursor.execute('PRAGMA index_info(%s)' % quote_name(index))
496-        info = cursor.fetchall()
497-        # Skip indexes across multiple fields
498-        if len(info) != 1:
499-            continue
500-        name = info[0][2] # seqno, cid, name
501-        indexes[name]['unique'] = True
502-    return indexes
503-
504-def _table_info(cursor, name):
505-    cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
506-    # cid, name, type, notnull, dflt_value, pk
507-    return [{'name': field[1],
508-             'type': field[2],
509-             'null_ok': not field[3],
510-             'pk': field[5]     # undocumented
511-             } for field in cursor.fetchall()]
512-
513-# Maps SQL types to Django Field types. Some of the SQL types have multiple
514-# entries here because SQLite allows for anything and doesn't normalize the
515-# field type; it uses whatever was given.
516-BASE_DATA_TYPES_REVERSE = {
517-    'bool': 'BooleanField',
518-    'boolean': 'BooleanField',
519-    'smallint': 'SmallIntegerField',
520-    'smallinteger': 'SmallIntegerField',
521-    'int': 'IntegerField',
522-    'integer': 'IntegerField',
523-    'text': 'TextField',
524-    'char': 'CharField',
525-    'date': 'DateField',
526-    'datetime': 'DateTimeField',
527-    'time': 'TimeField',
528-}
529-
530 # This light wrapper "fakes" a dictionary interface, because some SQLite data
531 # types include variables in them -- e.g. "varchar(30)" -- and can't be matched
532 # as a simple dictionary lookup.
533 class FlexibleFieldLookupDict:
534+    # Maps SQL types to Django Field types. Some of the SQL types have multiple
535+    # entries here because SQLite allows for anything and doesn't normalize the
536+    # field type; it uses whatever was given.
537+    base_data_types_reverse = {
538+        'bool': 'BooleanField',
539+        'boolean': 'BooleanField',
540+        'smallint': 'SmallIntegerField',
541+        'smallinteger': 'SmallIntegerField',
542+        'int': 'IntegerField',
543+        'integer': 'IntegerField',
544+        'text': 'TextField',
545+        'char': 'CharField',
546+        'date': 'DateField',
547+        'datetime': 'DateTimeField',
548+        'time': 'TimeField',
549+    }
550+
551     def __getitem__(self, key):
552         key = key.lower()
553         try:
554-            return BASE_DATA_TYPES_REVERSE[key]
555+            return self.base_data_types_reverse[key]
556         except KeyError:
557             import re
558             m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
559@@ -86,4 +32,61 @@
560                 return ('CharField', {'max_length': int(m.group(1))})
561             raise KeyError
562 
563-DATA_TYPES_REVERSE = FlexibleFieldLookupDict()
564+class DatabaseIntrospection(BaseDatabaseIntrospection):
565+    data_types_reverse = FlexibleFieldLookupDict()
566+
567+    def __init__(self, ops):
568+        self.ops = ops
569+       
570+    def get_table_list(self, cursor):
571+        "Returns a list of table names in the current database."
572+        # Skip the sqlite_sequence system table used for autoincrement key
573+        # generation.
574+        cursor.execute("""
575+            SELECT name FROM sqlite_master
576+            WHERE type='table' AND NOT name='sqlite_sequence'
577+            ORDER BY name""")
578+        return [row[0] for row in cursor.fetchall()]
579+
580+    def get_table_description(self, cursor, table_name):
581+        "Returns a description of the table, with the DB-API cursor.description interface."
582+        return [(info['name'], info['type'], None, None, None, None,
583+                 info['null_ok']) for info in self._table_info(cursor, table_name)]
584+
585+    def get_relations(self, cursor, table_name):
586+        raise NotImplementedError
587+
588+    def get_indexes(self, cursor, table_name):
589+        """
590+        Returns a dictionary of fieldname -> infodict for the given table,
591+        where each infodict is in the format:
592+            {'primary_key': boolean representing whether it's the primary key,
593+             'unique': boolean representing whether it's a unique index}
594+        """
595+        indexes = {}
596+        for info in self._table_info(cursor, table_name):
597+            indexes[info['name']] = {'primary_key': info['pk'] != 0,
598+                                     'unique': False}
599+        cursor.execute('PRAGMA index_list(%s)' % self.ops.quote_name(table_name))
600+        # seq, name, unique
601+        for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
602+            if not unique:
603+                continue
604+            cursor.execute('PRAGMA index_info(%s)' % self.ops.quote_name(index))
605+            info = cursor.fetchall()
606+            # Skip indexes across multiple fields
607+            if len(info) != 1:
608+                continue
609+            name = info[0][2] # seqno, cid, name
610+            indexes[name]['unique'] = True
611+        return indexes
612+
613+    def _table_info(self, cursor, name):
614+        cursor.execute('PRAGMA table_info(%s)' % self.ops.quote_name(name))
615+        # cid, name, type, notnull, dflt_value, pk
616+        return [{'name': field[1],
617+                 'type': field[2],
618+                 'null_ok': not field[3],
619+                 'pk': field[5]     # undocumented
620+                 } for field in cursor.fetchall()]
621+
622Index: django/db/backends/sqlite3/creation.py
623===================================================================
624--- django/db/backends/sqlite3/creation.py      (revision 8156)
625+++ django/db/backends/sqlite3/creation.py      (working copy)
626@@ -1,27 +1,30 @@
627-# SQLite doesn't actually support most of these types, but it "does the right
628-# thing" given more verbose field definitions, so leave them as is so that
629-# schema inspection is more useful.
630-DATA_TYPES = {
631-    'AutoField':                    'integer',
632-    'BooleanField':                 'bool',
633-    'CharField':                    'varchar(%(max_length)s)',
634-    'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',
635-    'DateField':                    'date',
636-    'DateTimeField':                'datetime',
637-    'DecimalField':                 'decimal',
638-    'FileField':                    'varchar(%(max_length)s)',
639-    'FilePathField':                'varchar(%(max_length)s)',
640-    'FloatField':                   'real',
641-    'IntegerField':                 'integer',
642-    'IPAddressField':               'char(15)',
643-    'NullBooleanField':             'bool',
644-    'OneToOneField':                'integer',
645-    'PhoneNumberField':             'varchar(20)',
646-    'PositiveIntegerField':         'integer unsigned',
647-    'PositiveSmallIntegerField':    'smallint unsigned',
648-    'SlugField':                    'varchar(%(max_length)s)',
649-    'SmallIntegerField':            'smallint',
650-    'TextField':                    'text',
651-    'TimeField':                    'time',
652-    'USStateField':                 'varchar(2)',
653-}
654+from django.db.backends import BaseDatabaseCreation
655+
656+class DatabaseCreation(BaseDatabaseCreation):
657+    # SQLite doesn't actually support most of these types, but it "does the right
658+    # thing" given more verbose field definitions, so leave them as is so that
659+    # schema inspection is more useful.
660+    data_types = {
661+        'AutoField':                    'integer',
662+        'BooleanField':                 'bool',
663+        'CharField':                    'varchar(%(max_length)s)',
664+        'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',
665+        'DateField':                    'date',
666+        'DateTimeField':                'datetime',
667+        'DecimalField':                 'decimal',
668+        'FileField':                    'varchar(%(max_length)s)',
669+        'FilePathField':                'varchar(%(max_length)s)',
670+        'FloatField':                   'real',
671+        'IntegerField':                 'integer',
672+        'IPAddressField':               'char(15)',
673+        'NullBooleanField':             'bool',
674+        'OneToOneField':                'integer',
675+        'PhoneNumberField':             'varchar(20)',
676+        'PositiveIntegerField':         'integer unsigned',
677+        'PositiveSmallIntegerField':    'smallint unsigned',
678+        'SlugField':                    'varchar(%(max_length)s)',
679+        'SmallIntegerField':            'smallint',
680+        'TextField':                    'text',
681+        'TimeField':                    'time',
682+        'USStateField':                 'varchar(2)',
683+    }
684Index: django/db/backends/mysql/base.py
685===================================================================
686--- django/db/backends/mysql/base.py    (revision 8156)
687+++ django/db/backends/mysql/base.py    (working copy)
688@@ -5,6 +5,10 @@
689 """
690 
691 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
692+from django.db.backends.mysql.client import DatabaseClient
693+from django.db.backends.mysql.creation import DatabaseCreation
694+from django.db.backends.mysql.introspection import DatabaseIntrospection
695+
696 try:
697     import MySQLdb as Database
698 except ImportError, e:
699@@ -144,6 +148,10 @@
700 class DatabaseWrapper(BaseDatabaseWrapper):
701     features = DatabaseFeatures()
702     ops = DatabaseOperations()
703+    client = DatabaseClient()
704+    creation = DatabaseCreation()
705+    introspection = DatabaseIntrospection(ops)
706+   
707     operators = {
708         'exact': '= BINARY %s',
709         'iexact': 'LIKE %s',
710Index: django/db/backends/mysql/client.py
711===================================================================
712--- django/db/backends/mysql/client.py  (revision 8156)
713+++ django/db/backends/mysql/client.py  (working copy)
714@@ -1,27 +1,29 @@
715+from django.db.backends import BaseDatabaseClient
716 from django.conf import settings
717 import os
718 
719-def runshell():
720-    args = ['']
721-    db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
722-    user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
723-    passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
724-    host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
725-    port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
726-    defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
727-    # Seems to be no good way to set sql_mode with CLI
728+class DatabaseClient(BaseDatabaseClient):
729+    def runshell(self):
730+        args = ['']
731+        db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
732+        user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
733+        passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
734+        host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
735+        port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
736+        defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
737+        # Seems to be no good way to set sql_mode with CLI
738     
739-    if defaults_file:
740-        args += ["--defaults-file=%s" % defaults_file]
741-    if user:
742-        args += ["--user=%s" % user]
743-    if passwd:
744-        args += ["--password=%s" % passwd]
745-    if host:
746-        args += ["--host=%s" % host]
747-    if port:
748-        args += ["--port=%s" % port]
749-    if db:
750-        args += [db]
751+        if defaults_file:
752+            args += ["--defaults-file=%s" % defaults_file]
753+        if user:
754+            args += ["--user=%s" % user]
755+        if passwd:
756+            args += ["--password=%s" % passwd]
757+        if host:
758+            args += ["--host=%s" % host]
759+        if port:
760+            args += ["--port=%s" % port]
761+        if db:
762+            args += [db]
763 
764-    os.execvp('mysql', args)
765+        os.execvp('mysql', args)
766Index: django/db/backends/mysql/introspection.py
767===================================================================
768--- django/db/backends/mysql/introspection.py   (revision 8156)
769+++ django/db/backends/mysql/introspection.py   (working copy)
770@@ -1,96 +1,100 @@
771-from django.db.backends.mysql.base import DatabaseOperations
772+from django.db.backends import BaseDatabaseIntrospection
773 from MySQLdb import ProgrammingError, OperationalError
774 from MySQLdb.constants import FIELD_TYPE
775 import re
776 
777-quote_name = DatabaseOperations().quote_name
778 foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
779 
780-def get_table_list(cursor):
781-    "Returns a list of table names in the current database."
782-    cursor.execute("SHOW TABLES")
783-    return [row[0] for row in cursor.fetchall()]
784+class DatabaseIntrospection(BaseDatabaseIntrospection):
785+    data_types_reverse = {
786+        FIELD_TYPE.BLOB: 'TextField',
787+        FIELD_TYPE.CHAR: 'CharField',
788+        FIELD_TYPE.DECIMAL: 'DecimalField',
789+        FIELD_TYPE.DATE: 'DateField',
790+        FIELD_TYPE.DATETIME: 'DateTimeField',
791+        FIELD_TYPE.DOUBLE: 'FloatField',
792+        FIELD_TYPE.FLOAT: 'FloatField',
793+        FIELD_TYPE.INT24: 'IntegerField',
794+        FIELD_TYPE.LONG: 'IntegerField',
795+        FIELD_TYPE.LONGLONG: 'IntegerField',
796+        FIELD_TYPE.SHORT: 'IntegerField',
797+        FIELD_TYPE.STRING: 'CharField',
798+        FIELD_TYPE.TIMESTAMP: 'DateTimeField',
799+        FIELD_TYPE.TINY: 'IntegerField',
800+        FIELD_TYPE.TINY_BLOB: 'TextField',
801+        FIELD_TYPE.MEDIUM_BLOB: 'TextField',
802+        FIELD_TYPE.LONG_BLOB: 'TextField',
803+        FIELD_TYPE.VAR_STRING: 'CharField',
804+    }
805 
806-def get_table_description(cursor, table_name):
807-    "Returns a description of the table, with the DB-API cursor.description interface."
808-    cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
809-    return cursor.description
810+    def __init__(self, ops):
811+        self.ops = ops
812+       
813+    def get_table_list(self, cursor):
814+        "Returns a list of table names in the current database."
815+        cursor.execute("SHOW TABLES")
816+        return [row[0] for row in cursor.fetchall()]
817 
818-def _name_to_index(cursor, table_name):
819-    """
820-    Returns a dictionary of {field_name: field_index} for the given table.
821-    Indexes are 0-based.
822-    """
823-    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
824+    def get_table_description(self, cursor, table_name):
825+        "Returns a description of the table, with the DB-API cursor.description interface."
826+        cursor.execute("SELECT * FROM %s LIMIT 1" % self.ops.quote_name(table_name))
827+        return cursor.description
828 
829-def get_relations(cursor, table_name):
830-    """
831-    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
832-    representing all relationships to the given table. Indexes are 0-based.
833-    """
834-    my_field_dict = _name_to_index(cursor, table_name)
835-    constraints = []
836-    relations = {}
837-    try:
838-        # This should work for MySQL 5.0.
839-        cursor.execute("""
840-            SELECT column_name, referenced_table_name, referenced_column_name
841-            FROM information_schema.key_column_usage
842-            WHERE table_name = %s
843-                AND table_schema = DATABASE()
844-                AND referenced_table_name IS NOT NULL
845-                AND referenced_column_name IS NOT NULL""", [table_name])
846-        constraints.extend(cursor.fetchall())
847-    except (ProgrammingError, OperationalError):
848-        # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
849-        # Go through all constraints and save the equal matches.
850-        cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
851-        for row in cursor.fetchall():
852-            pos = 0
853-            while True:
854-                match = foreign_key_re.search(row[1], pos)
855-                if match == None:
856-                    break
857-                pos = match.end()
858-                constraints.append(match.groups())
859+    def _name_to_index(self, cursor, table_name):
860+        """
861+        Returns a dictionary of {field_name: field_index} for the given table.
862+        Indexes are 0-based.
863+        """
864+        return dict([(d[0], i) for i, d in enumerate(self.get_table_description(cursor, table_name))])
865 
866-    for my_fieldname, other_table, other_field in constraints:
867-        other_field_index = _name_to_index(cursor, other_table)[other_field]
868-        my_field_index = my_field_dict[my_fieldname]
869-        relations[my_field_index] = (other_field_index, other_table)
870+    def get_relations(self, cursor, table_name):
871+        """
872+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
873+        representing all relationships to the given table. Indexes are 0-based.
874+        """
875+        my_field_dict = self._name_to_index(cursor, table_name)
876+        constraints = []
877+        relations = {}
878+        try:
879+            # This should work for MySQL 5.0.
880+            cursor.execute("""
881+                SELECT column_name, referenced_table_name, referenced_column_name
882+                FROM information_schema.key_column_usage
883+                WHERE table_name = %s
884+                    AND table_schema = DATABASE()
885+                    AND referenced_table_name IS NOT NULL
886+                    AND referenced_column_name IS NOT NULL""", [table_name])
887+            constraints.extend(cursor.fetchall())
888+        except (ProgrammingError, OperationalError):
889+            # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
890+            # Go through all constraints and save the equal matches.
891+            cursor.execute("SHOW CREATE TABLE %s" % self.ops.quote_name(table_name))
892+            for row in cursor.fetchall():
893+                pos = 0
894+                while True:
895+                    match = foreign_key_re.search(row[1], pos)
896+                    if match == None:
897+                        break
898+                    pos = match.end()
899+                    constraints.append(match.groups())
900 
901-    return relations
902+        for my_fieldname, other_table, other_field in constraints:
903+            other_field_index = self._name_to_index(cursor, other_table)[other_field]
904+            my_field_index = my_field_dict[my_fieldname]
905+            relations[my_field_index] = (other_field_index, other_table)
906 
907-def get_indexes(cursor, table_name):
908-    """
909-    Returns a dictionary of fieldname -> infodict for the given table,
910-    where each infodict is in the format:
911-        {'primary_key': boolean representing whether it's the primary key,
912-         'unique': boolean representing whether it's a unique index}
913-    """
914-    cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
915-    indexes = {}
916-    for row in cursor.fetchall():
917-        indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
918-    return indexes
919+        return relations
920 
921-DATA_TYPES_REVERSE = {
922-    FIELD_TYPE.BLOB: 'TextField',
923-    FIELD_TYPE.CHAR: 'CharField',
924-    FIELD_TYPE.DECIMAL: 'DecimalField',
925-    FIELD_TYPE.DATE: 'DateField',
926-    FIELD_TYPE.DATETIME: 'DateTimeField',
927-    FIELD_TYPE.DOUBLE: 'FloatField',
928-    FIELD_TYPE.FLOAT: 'FloatField',
929-    FIELD_TYPE.INT24: 'IntegerField',
930-    FIELD_TYPE.LONG: 'IntegerField',
931-    FIELD_TYPE.LONGLONG: 'IntegerField',
932-    FIELD_TYPE.SHORT: 'IntegerField',
933-    FIELD_TYPE.STRING: 'CharField',
934-    FIELD_TYPE.TIMESTAMP: 'DateTimeField',
935-    FIELD_TYPE.TINY: 'IntegerField',
936-    FIELD_TYPE.TINY_BLOB: 'TextField',
937-    FIELD_TYPE.MEDIUM_BLOB: 'TextField',
938-    FIELD_TYPE.LONG_BLOB: 'TextField',
939-    FIELD_TYPE.VAR_STRING: 'CharField',
940-}
941+    def get_indexes(self, cursor, table_name):
942+        """
943+        Returns a dictionary of fieldname -> infodict for the given table,
944+        where each infodict is in the format:
945+            {'primary_key': boolean representing whether it's the primary key,
946+             'unique': boolean representing whether it's a unique index}
947+        """
948+        cursor.execute("SHOW INDEX FROM %s" % self.ops.quote_name(table_name))
949+        indexes = {}
950+        for row in cursor.fetchall():
951+            indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
952+        return indexes
953+
954Index: django/db/backends/mysql/creation.py
955===================================================================
956--- django/db/backends/mysql/creation.py        (revision 8156)
957+++ django/db/backends/mysql/creation.py        (working copy)
958@@ -1,28 +1,31 @@
959-# This dictionary maps Field objects to their associated MySQL column
960-# types, as strings. Column-type strings can contain format strings; they'll
961-# be interpolated against the values of Field.__dict__ before being output.
962-# If a column type is set to None, it won't be included in the output.
963-DATA_TYPES = {
964-    'AutoField':         'integer AUTO_INCREMENT',
965-    'BooleanField':      'bool',
966-    'CharField':         'varchar(%(max_length)s)',
967-    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
968-    'DateField':         'date',
969-    'DateTimeField':     'datetime',
970-    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
971-    'FileField':         'varchar(%(max_length)s)',
972-    'FilePathField':     'varchar(%(max_length)s)',
973-    'FloatField':        'double precision',
974-    'IntegerField':      'integer',
975-    'IPAddressField':    'char(15)',
976-    'NullBooleanField':  'bool',
977-    'OneToOneField':     'integer',
978-    'PhoneNumberField':  'varchar(20)',
979-    'PositiveIntegerField': 'integer UNSIGNED',
980-    'PositiveSmallIntegerField': 'smallint UNSIGNED',
981-    'SlugField':         'varchar(%(max_length)s)',
982-    'SmallIntegerField': 'smallint',
983-    'TextField':         'longtext',
984-    'TimeField':         'time',
985-    'USStateField':      'varchar(2)',
986-}
987+from django.db.backends import BaseDatabaseCreation
988+
989+class DatabaseCreation(BaseDatabaseCreation):
990+    # This dictionary maps Field objects to their associated MySQL column
991+    # types, as strings. Column-type strings can contain format strings; they'll
992+    # be interpolated against the values of Field.__dict__ before being output.
993+    # If a column type is set to None, it won't be included in the output.
994+    data_types = {
995+        'AutoField':         'integer AUTO_INCREMENT',
996+        'BooleanField':      'bool',
997+        'CharField':         'varchar(%(max_length)s)',
998+        'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
999+        'DateField':         'date',
1000+        'DateTimeField':     'datetime',
1001+        'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
1002+        'FileField':         'varchar(%(max_length)s)',
1003+        'FilePathField':     'varchar(%(max_length)s)',
1004+        'FloatField':        'double precision',
1005+        'IntegerField':      'integer',
1006+        'IPAddressField':    'char(15)',
1007+        'NullBooleanField':  'bool',
1008+        'OneToOneField':     'integer',
1009+        'PhoneNumberField':  'varchar(20)',
1010+        'PositiveIntegerField': 'integer UNSIGNED',
1011+        'PositiveSmallIntegerField': 'smallint UNSIGNED',
1012+        'SlugField':         'varchar(%(max_length)s)',
1013+        'SmallIntegerField': 'smallint',
1014+        'TextField':         'longtext',
1015+        'TimeField':         'time',
1016+        'USStateField':      'varchar(2)',
1017+    }
1018Index: django/db/backends/oracle/base.py
1019===================================================================
1020--- django/db/backends/oracle/base.py   (revision 8156)
1021+++ django/db/backends/oracle/base.py   (working copy)
1022@@ -10,6 +10,9 @@
1023 
1024 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
1025 from django.db.backends.oracle import query
1026+from django.db.backends.oracle.client import DatabaseClient
1027+from django.db.backends.oracle.creation import DatabaseCreation
1028+from django.db.backends.oracle.introspection import DatabaseIntrospection
1029 from django.utils.encoding import smart_str, force_unicode
1030 
1031 # Oracle takes client-side character set encoding from the environment.
1032@@ -198,6 +201,10 @@
1033 class DatabaseWrapper(BaseDatabaseWrapper):
1034     features = DatabaseFeatures()
1035     ops = DatabaseOperations()
1036+    client = DatabaseClient()
1037+    creation = DatabaseCreation()
1038+    introspection = DatabaseIntrospection(ops)
1039+   
1040     operators = {
1041         'exact': '= %s',
1042         'iexact': '= UPPER(%s)',
1043Index: django/db/backends/oracle/client.py
1044===================================================================
1045--- django/db/backends/oracle/client.py (revision 8156)
1046+++ django/db/backends/oracle/client.py (working copy)
1047@@ -1,11 +1,13 @@
1048+from django.db.backends import BaseDatabaseClient
1049 from django.conf import settings
1050 import os
1051 
1052-def runshell():
1053-    dsn = settings.DATABASE_USER
1054-    if settings.DATABASE_PASSWORD:
1055-        dsn += "/%s" % settings.DATABASE_PASSWORD
1056-    if settings.DATABASE_NAME:
1057-        dsn += "@%s" % settings.DATABASE_NAME
1058-    args = ["sqlplus", "-L", dsn]
1059-    os.execvp("sqlplus", args)
1060+class DatabaseClient(BaseDatabaseClient):
1061+    def runshell(self):
1062+        dsn = settings.DATABASE_USER
1063+        if settings.DATABASE_PASSWORD:
1064+            dsn += "/%s" % settings.DATABASE_PASSWORD
1065+        if settings.DATABASE_NAME:
1066+            dsn += "@%s" % settings.DATABASE_NAME
1067+        args = ["sqlplus", "-L", dsn]
1068+        os.execvp("sqlplus", args)
1069Index: django/db/backends/oracle/introspection.py
1070===================================================================
1071--- django/db/backends/oracle/introspection.py  (revision 8156)
1072+++ django/db/backends/oracle/introspection.py  (working copy)
1073@@ -1,98 +1,102 @@
1074-from django.db.backends.oracle.base import DatabaseOperations
1075+from django.db.backends import BaseDatabaseIntrospection
1076+import cx_Oracle
1077 import re
1078-import cx_Oracle
1079 
1080-quote_name = DatabaseOperations().quote_name
1081 foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
1082 
1083-def get_table_list(cursor):
1084-    "Returns a list of table names in the current database."
1085-    cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
1086-    return [row[0].upper() for row in cursor.fetchall()]
1087+class DatabaseIntrospection(BaseDatabaseIntrospection):
1088+    # Maps type objects to Django Field types.
1089+    data_types_reverse = {
1090+        cx_Oracle.CLOB: 'TextField',
1091+        cx_Oracle.DATETIME: 'DateTimeField',
1092+        cx_Oracle.FIXED_CHAR: 'CharField',
1093+        cx_Oracle.NCLOB: 'TextField',
1094+        cx_Oracle.NUMBER: 'DecimalField',
1095+        cx_Oracle.STRING: 'CharField',
1096+        cx_Oracle.TIMESTAMP: 'DateTimeField',
1097+    }
1098 
1099-def get_table_description(cursor, table_name):
1100-    "Returns a description of the table, with the DB-API cursor.description interface."
1101-    cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % quote_name(table_name))
1102-    return cursor.description
1103+    def __init__(self, ops):
1104+        self.ops = ops
1105+       
1106+    def get_table_list(self, cursor):
1107+        "Returns a list of table names in the current database."
1108+        cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
1109+        return [row[0].upper() for row in cursor.fetchall()]
1110 
1111-def _name_to_index(cursor, table_name):
1112-    """
1113-    Returns a dictionary of {field_name: field_index} for the given table.
1114-    Indexes are 0-based.
1115-    """
1116-    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
1117+    def get_table_description(self, cursor, table_name):
1118+        "Returns a description of the table, with the DB-API cursor.description interface."
1119+        cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % self.ops.quote_name(table_name))
1120+        return cursor.description
1121 
1122-def get_relations(cursor, table_name):
1123-    """
1124-    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1125-    representing all relationships to the given table. Indexes are 0-based.
1126-    """
1127-    cursor.execute("""
1128-SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
1129-FROM   user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
1130-       user_tab_cols ta, user_tab_cols tb
1131-WHERE  user_constraints.table_name = %s AND
1132-       ta.table_name = %s AND
1133-       ta.column_name = ca.column_name AND
1134-       ca.table_name = %s AND
1135-       user_constraints.constraint_name = ca.constraint_name AND
1136-       user_constraints.r_constraint_name = cb.constraint_name AND
1137-       cb.table_name = tb.table_name AND
1138-       cb.column_name = tb.column_name AND
1139-       ca.position = cb.position""", [table_name, table_name, table_name])
1140+    def _name_to_index(self, cursor, table_name):
1141+        """
1142+        Returns a dictionary of {field_name: field_index} for the given table.
1143+        Indexes are 0-based.
1144+        """
1145+        return dict([(d[0], i) for i, d in enumerate(self.get_table_description(cursor, table_name))])
1146 
1147-    relations = {}
1148-    for row in cursor.fetchall():
1149-        relations[row[0]] = (row[2], row[1])
1150-    return relations
1151+    def get_relations(self, cursor, table_name):
1152+        """
1153+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1154+        representing all relationships to the given table. Indexes are 0-based.
1155+        """
1156+        cursor.execute("""
1157+    SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
1158+    FROM   user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
1159+           user_tab_cols ta, user_tab_cols tb
1160+    WHERE  user_constraints.table_name = %s AND
1161+           ta.table_name = %s AND
1162+           ta.column_name = ca.column_name AND
1163+           ca.table_name = %s AND
1164+           user_constraints.constraint_name = ca.constraint_name AND
1165+           user_constraints.r_constraint_name = cb.constraint_name AND
1166+           cb.table_name = tb.table_name AND
1167+           cb.column_name = tb.column_name AND
1168+           ca.position = cb.position""", [table_name, table_name, table_name])
1169 
1170-def get_indexes(cursor, table_name):
1171-    """
1172-    Returns a dictionary of fieldname -> infodict for the given table,
1173-    where each infodict is in the format:
1174-        {'primary_key': boolean representing whether it's the primary key,
1175-         'unique': boolean representing whether it's a unique index}
1176-    """
1177-    # This query retrieves each index on the given table, including the
1178-    # first associated field name
1179-    # "We were in the nick of time; you were in great peril!"
1180-    sql = """
1181-WITH primarycols AS (
1182- SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
1183- FROM   user_cons_columns, user_constraints
1184- WHERE  user_cons_columns.constraint_name = user_constraints.constraint_name AND
1185-        user_constraints.constraint_type = 'P' AND
1186-        user_cons_columns.table_name = %s),
1187- uniquecols AS (
1188- SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
1189- FROM   user_indexes, user_ind_columns
1190- WHERE  uniqueness = 'UNIQUE' AND
1191-        user_indexes.index_name = user_ind_columns.index_name AND
1192-        user_ind_columns.table_name = %s)
1193-SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
1194-FROM   (SELECT column_name FROM primarycols UNION SELECT column_name FROM
1195-uniquecols) allcols,
1196-      primarycols, uniquecols
1197-WHERE  allcols.column_name = primarycols.column_name (+) AND
1198-      allcols.column_name = uniquecols.column_name (+)
1199-    """
1200-    cursor.execute(sql, [table_name, table_name])
1201-    indexes = {}
1202-    for row in cursor.fetchall():
1203-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
1204-        # a string of space-separated integers. This designates the field
1205-        # indexes (1-based) of the fields that have indexes on the table.
1206-        # Here, we skip any indexes across multiple fields.
1207-        indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
1208-    return indexes
1209+        relations = {}
1210+        for row in cursor.fetchall():
1211+            relations[row[0]] = (row[2], row[1])
1212+        return relations
1213 
1214-# Maps type objects to Django Field types.
1215-DATA_TYPES_REVERSE = {
1216-    cx_Oracle.CLOB: 'TextField',
1217-    cx_Oracle.DATETIME: 'DateTimeField',
1218-    cx_Oracle.FIXED_CHAR: 'CharField',
1219-    cx_Oracle.NCLOB: 'TextField',
1220-    cx_Oracle.NUMBER: 'DecimalField',
1221-    cx_Oracle.STRING: 'CharField',
1222-    cx_Oracle.TIMESTAMP: 'DateTimeField',
1223-}
1224+    def get_indexes(self, cursor, table_name):
1225+        """
1226+        Returns a dictionary of fieldname -> infodict for the given table,
1227+        where each infodict is in the format:
1228+            {'primary_key': boolean representing whether it's the primary key,
1229+             'unique': boolean representing whether it's a unique index}
1230+        """
1231+        # This query retrieves each index on the given table, including the
1232+        # first associated field name
1233+        # "We were in the nick of time; you were in great peril!"
1234+        sql = """
1235+    WITH primarycols AS (
1236+     SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
1237+     FROM   user_cons_columns, user_constraints
1238+     WHERE  user_cons_columns.constraint_name = user_constraints.constraint_name AND
1239+            user_constraints.constraint_type = 'P' AND
1240+            user_cons_columns.table_name = %s),
1241+     uniquecols AS (
1242+     SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
1243+     FROM   user_indexes, user_ind_columns
1244+     WHERE  uniqueness = 'UNIQUE' AND
1245+            user_indexes.index_name = user_ind_columns.index_name AND
1246+            user_ind_columns.table_name = %s)
1247+    SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
1248+    FROM   (SELECT column_name FROM primarycols UNION SELECT column_name FROM
1249+    uniquecols) allcols,
1250+          primarycols, uniquecols
1251+    WHERE  allcols.column_name = primarycols.column_name (+) AND
1252+          allcols.column_name = uniquecols.column_name (+)
1253+        """
1254+        cursor.execute(sql, [table_name, table_name])
1255+        indexes = {}
1256+        for row in cursor.fetchall():
1257+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
1258+            # a string of space-separated integers. This designates the field
1259+            # indexes (1-based) of the fields that have indexes on the table.
1260+            # Here, we skip any indexes across multiple fields.
1261+            indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
1262+        return indexes
1263+
1264Index: django/db/backends/oracle/creation.py
1265===================================================================
1266--- django/db/backends/oracle/creation.py       (revision 8156)
1267+++ django/db/backends/oracle/creation.py       (working copy)
1268@@ -1,291 +1,292 @@
1269 import sys, time
1270 from django.core import management
1271+from django.db.backends import BaseDatabaseCreation
1272 
1273-# This dictionary maps Field objects to their associated Oracle column
1274-# types, as strings. Column-type strings can contain format strings; they'll
1275-# be interpolated against the values of Field.__dict__ before being output.
1276-# If a column type is set to None, it won't be included in the output.
1277-#
1278-# Any format strings starting with "qn_" are quoted before being used in the
1279-# output (the "qn_" prefix is stripped before the lookup is performed.
1280-
1281-DATA_TYPES = {
1282-    'AutoField':                    'NUMBER(11)',
1283-    'BooleanField':                 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
1284-    'CharField':                    'NVARCHAR2(%(max_length)s)',
1285-    'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',
1286-    'DateField':                    'DATE',
1287-    'DateTimeField':                'TIMESTAMP',
1288-    'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
1289-    'FileField':                    'NVARCHAR2(%(max_length)s)',
1290-    'FilePathField':                'NVARCHAR2(%(max_length)s)',
1291-    'FloatField':                   'DOUBLE PRECISION',
1292-    'IntegerField':                 'NUMBER(11)',
1293-    'IPAddressField':               'VARCHAR2(15)',
1294-    'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
1295-    'OneToOneField':                'NUMBER(11)',
1296-    'PhoneNumberField':             'VARCHAR2(20)',
1297-    'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
1298-    'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(qn_column)s >= 0)',
1299-    'SlugField':                    'NVARCHAR2(50)',
1300-    'SmallIntegerField':            'NUMBER(11)',
1301-    'TextField':                    'NCLOB',
1302-    'TimeField':                    'TIMESTAMP',
1303-    'URLField':                     'VARCHAR2(%(max_length)s)',
1304-    'USStateField':                 'CHAR(2)',
1305-}
1306-
1307 TEST_DATABASE_PREFIX = 'test_'
1308 PASSWORD = 'Im_a_lumberjack'
1309-REMEMBER = {}
1310 
1311-def create_test_db(settings, connection, verbosity=1, autoclobber=False):
1312-    TEST_DATABASE_NAME = _test_database_name(settings)
1313-    TEST_DATABASE_USER = _test_database_user(settings)
1314-    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
1315-    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
1316-    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
1317+class DatabaseCreation(BaseDatabaseCreation):
1318+    # This dictionary maps Field objects to their associated Oracle column
1319+    # types, as strings. Column-type strings can contain format strings; they'll
1320+    # be interpolated against the values of Field.__dict__ before being output.
1321+    # If a column type is set to None, it won't be included in the output.
1322+    #
1323+    # Any format strings starting with "qn_" are quoted before being used in the
1324+    # output (the "qn_" prefix is stripped before the lookup is performed.
1325 
1326-    parameters = {
1327-        'dbname': TEST_DATABASE_NAME,
1328-        'user': TEST_DATABASE_USER,
1329-        'password': TEST_DATABASE_PASSWD,
1330-        'tblspace': TEST_DATABASE_TBLSPACE,
1331-        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1332-       }
1333+    data_types = {
1334+        'AutoField':                    'NUMBER(11)',
1335+        'BooleanField':                 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
1336+        'CharField':                    'NVARCHAR2(%(max_length)s)',
1337+        'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',
1338+        'DateField':                    'DATE',
1339+        'DateTimeField':                'TIMESTAMP',
1340+        'DecimalField':                 'NUMBER(%(max_digits)s, %(decimal_places)s)',
1341+        'FileField':                    'NVARCHAR2(%(max_length)s)',
1342+        'FilePathField':                'NVARCHAR2(%(max_length)s)',
1343+        'FloatField':                   'DOUBLE PRECISION',
1344+        'IntegerField':                 'NUMBER(11)',
1345+        'IPAddressField':               'VARCHAR2(15)',
1346+        'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
1347+        'OneToOneField':                'NUMBER(11)',
1348+        'PhoneNumberField':             'VARCHAR2(20)',
1349+        'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
1350+        'PositiveSmallIntegerField':    'NUMBER(11) CHECK (%(qn_column)s >= 0)',
1351+        'SlugField':                    'NVARCHAR2(50)',
1352+        'SmallIntegerField':            'NUMBER(11)',
1353+        'TextField':                    'NCLOB',
1354+        'TimeField':                    'TIMESTAMP',
1355+        'URLField':                     'VARCHAR2(%(max_length)s)',
1356+        'USStateField':                 'CHAR(2)',
1357+    }
1358+   
1359+    def create_test_db(settings, connection, verbosity=1, autoclobber=False):
1360+        TEST_DATABASE_NAME = self._test_database_name(settings)
1361+        TEST_DATABASE_USER = self._test_database_user(settings)
1362+        TEST_DATABASE_PASSWD = self._test_database_passwd(settings)
1363+        TEST_DATABASE_TBLSPACE = self._test_database_tblspace(settings)
1364+        TEST_DATABASE_TBLSPACE_TMP = self._test_database_tblspace_tmp(settings)
1365 
1366-    REMEMBER['user'] = settings.DATABASE_USER
1367-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
1368+        parameters = {
1369+            'dbname': TEST_DATABASE_NAME,
1370+            'user': TEST_DATABASE_USER,
1371+            'password': TEST_DATABASE_PASSWD,
1372+            'tblspace': TEST_DATABASE_TBLSPACE,
1373+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1374+       }
1375 
1376-    cursor = connection.cursor()
1377-    if _test_database_create(settings):
1378-        if verbosity >= 1:
1379-            print 'Creating test database...'
1380-        try:
1381-            _create_test_db(cursor, parameters, verbosity)
1382-        except Exception, e:
1383-            sys.stderr.write("Got an error creating the test database: %s\n" % e)
1384-            if not autoclobber:
1385-                confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
1386-            if autoclobber or confirm == 'yes':
1387-                try:
1388-                    if verbosity >= 1:
1389-                        print "Destroying old test database..."
1390-                    _destroy_test_db(cursor, parameters, verbosity)
1391-                    if verbosity >= 1:
1392-                        print "Creating test database..."
1393-                    _create_test_db(cursor, parameters, verbosity)
1394-                except Exception, e:
1395-                    sys.stderr.write("Got an error recreating the test database: %s\n" % e)
1396-                    sys.exit(2)
1397-            else:
1398-                print "Tests cancelled."
1399-                sys.exit(1)
1400+        self.remember['user'] = settings.DATABASE_USER
1401+        self.remember['passwd'] = settings.DATABASE_PASSWORD
1402 
1403-    if _test_user_create(settings):
1404-        if verbosity >= 1:
1405-            print "Creating test user..."
1406-        try:
1407-            _create_test_user(cursor, parameters, verbosity)
1408-        except Exception, e:
1409-            sys.stderr.write("Got an error creating the test user: %s\n" % e)
1410-            if not autoclobber:
1411-                confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
1412-            if autoclobber or confirm == 'yes':
1413-                try:
1414-                    if verbosity >= 1:
1415-                        print "Destroying old test user..."
1416-                    _destroy_test_user(cursor, parameters, verbosity)
1417-                    if verbosity >= 1:
1418-                        print "Creating test user..."
1419-                    _create_test_user(cursor, parameters, verbosity)
1420-                except Exception, e:
1421-                    sys.stderr.write("Got an error recreating the test user: %s\n" % e)
1422-                    sys.exit(2)
1423-            else:
1424-                print "Tests cancelled."
1425-                sys.exit(1)
1426+        cursor = connection.cursor()
1427+        if self._test_database_create(settings):
1428+            if verbosity >= 1:
1429+                print 'Creating test database...'
1430+            try:
1431+                self._create_test_db(cursor, parameters, verbosity)
1432+            except Exception, e:
1433+                sys.stderr.write("Got an error creating the test database: %s\n" % e)
1434+                if not autoclobber:
1435+                    confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
1436+                if autoclobber or confirm == 'yes':
1437+                    try:
1438+                        if verbosity >= 1:
1439+                            print "Destroying old test database..."
1440+                        self._destroy_test_db(cursor, parameters, verbosity)
1441+                        if verbosity >= 1:
1442+                            print "Creating test database..."
1443+                        self._create_test_db(cursor, parameters, verbosity)
1444+                    except Exception, e:
1445+                        sys.stderr.write("Got an error recreating the test database: %s\n" % e)
1446+                        sys.exit(2)
1447+                else:
1448+                    print "Tests cancelled."
1449+                    sys.exit(1)
1450 
1451-    connection.close()
1452-    settings.DATABASE_USER = TEST_DATABASE_USER
1453-    settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
1454+        if self._test_user_create(settings):
1455+            if verbosity >= 1:
1456+                print "Creating test user..."
1457+            try:
1458+                self._create_test_user(cursor, parameters, verbosity)
1459+            except Exception, e:
1460+                sys.stderr.write("Got an error creating the test user: %s\n" % e)
1461+                if not autoclobber:
1462+                    confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
1463+                if autoclobber or confirm == 'yes':
1464+                    try:
1465+                        if verbosity >= 1:
1466+                            print "Destroying old test user..."
1467+                        self._destroy_test_user(cursor, parameters, verbosity)
1468+                        if verbosity >= 1:
1469+                            print "Creating test user..."
1470+                        self._create_test_user(cursor, parameters, verbosity)
1471+                    except Exception, e:
1472+                        sys.stderr.write("Got an error recreating the test user: %s\n" % e)
1473+                        sys.exit(2)
1474+                else:
1475+                    print "Tests cancelled."
1476+                    sys.exit(1)
1477 
1478-    management.call_command('syncdb', verbosity=verbosity, interactive=False)
1479+        connection.close()
1480+        settings.DATABASE_USER = TEST_DATABASE_USER
1481+        settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
1482 
1483-    # Get a cursor (even though we don't need one yet). This has
1484-    # the side effect of initializing the test database.
1485-    cursor = connection.cursor()
1486+        management.call_command('syncdb', verbosity=verbosity, interactive=False)
1487 
1488-def destroy_test_db(settings, connection, old_database_name, verbosity=1):
1489-    connection.close()
1490+        # Get a cursor (even though we don't need one yet). This has
1491+        # the side effect of initializing the test database.
1492+        cursor = connection.cursor()
1493 
1494-    TEST_DATABASE_NAME = _test_database_name(settings)
1495-    TEST_DATABASE_USER = _test_database_user(settings)
1496-    TEST_DATABASE_PASSWD = _test_database_passwd(settings)
1497-    TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
1498-    TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
1499+    def destroy_test_db(settings, connection, old_database_name, verbosity=1):
1500+        connection.close()
1501 
1502-    settings.DATABASE_NAME = old_database_name
1503-    settings.DATABASE_USER = REMEMBER['user']
1504-    settings.DATABASE_PASSWORD = REMEMBER['passwd']
1505+        TEST_DATABASE_NAME = self._test_database_name(settings)
1506+        TEST_DATABASE_USER = self._test_database_user(settings)
1507+        TEST_DATABASE_PASSWD = self._test_database_passwd(settings)
1508+        TEST_DATABASE_TBLSPACE = self._test_database_tblspace(settings)
1509+        TEST_DATABASE_TBLSPACE_TMP = self._test_database_tblspace_tmp(settings)
1510 
1511-    parameters = {
1512-        'dbname': TEST_DATABASE_NAME,
1513-        'user': TEST_DATABASE_USER,
1514-        'password': TEST_DATABASE_PASSWD,
1515-        'tblspace': TEST_DATABASE_TBLSPACE,
1516-        'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1517-       }
1518+        settings.DATABASE_NAME = old_database_name
1519+        settings.DATABASE_USER = self.remember['user']
1520+        settings.DATABASE_PASSWORD = self.remember['passwd']
1521 
1522-    REMEMBER['user'] = settings.DATABASE_USER
1523-    REMEMBER['passwd'] = settings.DATABASE_PASSWORD
1524+        parameters = {
1525+            'dbname': TEST_DATABASE_NAME,
1526+            'user': TEST_DATABASE_USER,
1527+            'password': TEST_DATABASE_PASSWD,
1528+            'tblspace': TEST_DATABASE_TBLSPACE,
1529+            'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
1530+       }
1531 
1532-    cursor = connection.cursor()
1533-    time.sleep(1) # To avoid "database is being accessed by other users" errors.
1534-    if _test_user_create(settings):
1535-        if verbosity >= 1:
1536-            print 'Destroying test user...'
1537-        _destroy_test_user(cursor, parameters, verbosity)
1538-    if _test_database_create(settings):
1539-        if verbosity >= 1:
1540-            print 'Destroying test database...'
1541-        _destroy_test_db(cursor, parameters, verbosity)
1542-    connection.close()
1543+        self.remember['user'] = settings.DATABASE_USER
1544+        self.remember['passwd'] = settings.DATABASE_PASSWORD
1545 
1546-def _create_test_db(cursor, parameters, verbosity):
1547-    if verbosity >= 2:
1548-        print "_create_test_db(): dbname = %s" % parameters['dbname']
1549-    statements = [
1550-        """CREATE TABLESPACE %(tblspace)s
1551-           DATAFILE '%(tblspace)s.dbf' SIZE 20M
1552-           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1553-        """,
1554-        """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
1555-           TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
1556-           REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1557-        """,
1558-    ]
1559-    _execute_statements(cursor, statements, parameters, verbosity)
1560+        cursor = connection.cursor()
1561+        time.sleep(1) # To avoid "database is being accessed by other users" errors.
1562+        if self._test_user_create(settings):
1563+            if verbosity >= 1:
1564+                print 'Destroying test user...'
1565+            self._destroy_test_user(cursor, parameters, verbosity)
1566+        if self._test_database_create(settings):
1567+            if verbosity >= 1:
1568+                print 'Destroying test database...'
1569+            self._destroy_test_db(cursor, parameters, verbosity)
1570+        connection.close()
1571 
1572-def _create_test_user(cursor, parameters, verbosity):
1573-    if verbosity >= 2:
1574-        print "_create_test_user(): username = %s" % parameters['user']
1575-    statements = [
1576-        """CREATE USER %(user)s
1577-           IDENTIFIED BY %(password)s
1578-           DEFAULT TABLESPACE %(tblspace)s
1579-           TEMPORARY TABLESPACE %(tblspace_temp)s
1580-        """,
1581-        """GRANT CONNECT, RESOURCE TO %(user)s""",
1582-    ]
1583-    _execute_statements(cursor, statements, parameters, verbosity)
1584+    def _create_test_db(cursor, parameters, verbosity):
1585+        if verbosity >= 2:
1586+            print "_create_test_db(): dbname = %s" % parameters['dbname']
1587+        statements = [
1588+            """CREATE TABLESPACE %(tblspace)s
1589+               DATAFILE '%(tblspace)s.dbf' SIZE 20M
1590+               REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1591+            """,
1592+            """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
1593+               TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
1594+               REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
1595+            """,
1596+        ]
1597+        _execute_statements(cursor, statements, parameters, verbosity)
1598 
1599-def _destroy_test_db(cursor, parameters, verbosity):
1600-    if verbosity >= 2:
1601-        print "_destroy_test_db(): dbname=%s" % parameters['dbname']
1602-    statements = [
1603-        'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1604-        'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1605+    def _create_test_user(cursor, parameters, verbosity):
1606+        if verbosity >= 2:
1607+            print "_create_test_user(): username = %s" % parameters['user']
1608+        statements = [
1609+            """CREATE USER %(user)s
1610+               IDENTIFIED BY %(password)s
1611+               DEFAULT TABLESPACE %(tblspace)s
1612+               TEMPORARY TABLESPACE %(tblspace_temp)s
1613+            """,
1614+            """GRANT CONNECT, RESOURCE TO %(user)s""",
1615         ]
1616-    _execute_statements(cursor, statements, parameters, verbosity)
1617+        _execute_statements(cursor, statements, parameters, verbosity)
1618 
1619-def _destroy_test_user(cursor, parameters, verbosity):
1620-    if verbosity >= 2:
1621-        print "_destroy_test_user(): user=%s" % parameters['user']
1622-        print "Be patient.  This can take some time..."
1623-    statements = [
1624-        'DROP USER %(user)s CASCADE',
1625-    ]
1626-    _execute_statements(cursor, statements, parameters, verbosity)
1627+    def _destroy_test_db(cursor, parameters, verbosity):
1628+        if verbosity >= 2:
1629+            print "_destroy_test_db(): dbname=%s" % parameters['dbname']
1630+        statements = [
1631+            'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1632+            'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
1633+            ]
1634+        _execute_statements(cursor, statements, parameters, verbosity)
1635 
1636-def _execute_statements(cursor, statements, parameters, verbosity):
1637-    for template in statements:
1638-        stmt = template % parameters
1639+    def _destroy_test_user(cursor, parameters, verbosity):
1640         if verbosity >= 2:
1641-            print stmt
1642+            print "_destroy_test_user(): user=%s" % parameters['user']
1643+            print "Be patient.  This can take some time..."
1644+        statements = [
1645+            'DROP USER %(user)s CASCADE',
1646+        ]
1647+        _execute_statements(cursor, statements, parameters, verbosity)
1648+
1649+    def _execute_statements(cursor, statements, parameters, verbosity):
1650+        for template in statements:
1651+            stmt = template % parameters
1652+            if verbosity >= 2:
1653+                print stmt
1654+            try:
1655+                cursor.execute(stmt)
1656+            except Exception, err:
1657+                sys.stderr.write("Failed (%s)\n" % (err))
1658+                raise
1659+
1660+    def _test_database_name(settings):
1661+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1662         try:
1663-            cursor.execute(stmt)
1664-        except Exception, err:
1665-            sys.stderr.write("Failed (%s)\n" % (err))
1666+            if settings.TEST_DATABASE_NAME:
1667+                name = settings.TEST_DATABASE_NAME
1668+        except AttributeError:
1669+            pass
1670+        except:
1671             raise
1672+        return name
1673 
1674-def _test_database_name(settings):
1675-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1676-    try:
1677-        if settings.TEST_DATABASE_NAME:
1678-            name = settings.TEST_DATABASE_NAME
1679-    except AttributeError:
1680-        pass
1681-    except:
1682-        raise
1683-    return name
1684+    def _test_database_create(settings):
1685+        name = True
1686+        try:
1687+            if settings.TEST_DATABASE_CREATE:
1688+                name = True
1689+            else:
1690+                name = False
1691+        except AttributeError:
1692+            pass
1693+        except:
1694+            raise
1695+        return name
1696 
1697-def _test_database_create(settings):
1698-    name = True
1699-    try:
1700-        if settings.TEST_DATABASE_CREATE:
1701-            name = True
1702-        else:
1703-            name = False
1704-    except AttributeError:
1705-        pass
1706-    except:
1707-        raise
1708-    return name
1709+    def _test_user_create(settings):
1710+        name = True
1711+        try:
1712+            if settings.TEST_USER_CREATE:
1713+                name = True
1714+            else:
1715+                name = False
1716+        except AttributeError:
1717+            pass
1718+        except:
1719+            raise
1720+        return name
1721 
1722-def _test_user_create(settings):
1723-    name = True
1724-    try:
1725-        if settings.TEST_USER_CREATE:
1726-            name = True
1727-        else:
1728-            name = False
1729-    except AttributeError:
1730-        pass
1731-    except:
1732-        raise
1733-    return name
1734+    def _test_database_user(settings):
1735+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1736+        try:
1737+            if settings.TEST_DATABASE_USER:
1738+                name = settings.TEST_DATABASE_USER
1739+        except AttributeError:
1740+            pass
1741+        except:
1742+            raise
1743+        return name
1744 
1745-def _test_database_user(settings):
1746-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1747-    try:
1748-        if settings.TEST_DATABASE_USER:
1749-            name = settings.TEST_DATABASE_USER
1750-    except AttributeError:
1751-        pass
1752-    except:
1753-        raise
1754-    return name
1755+    def _test_database_passwd(settings):
1756+        name = PASSWORD
1757+        try:
1758+            if settings.TEST_DATABASE_PASSWD:
1759+                name = settings.TEST_DATABASE_PASSWD
1760+        except AttributeError:
1761+            pass
1762+        except:
1763+            raise
1764+        return name
1765 
1766-def _test_database_passwd(settings):
1767-    name = PASSWORD
1768-    try:
1769-        if settings.TEST_DATABASE_PASSWD:
1770-            name = settings.TEST_DATABASE_PASSWD
1771-    except AttributeError:
1772-        pass
1773-    except:
1774-        raise
1775-    return name
1776+    def _test_database_tblspace(settings):
1777+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1778+        try:
1779+            if settings.TEST_DATABASE_TBLSPACE:
1780+                name = settings.TEST_DATABASE_TBLSPACE
1781+        except AttributeError:
1782+            pass
1783+        except:
1784+            raise
1785+        return name
1786 
1787-def _test_database_tblspace(settings):
1788-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
1789-    try:
1790-        if settings.TEST_DATABASE_TBLSPACE:
1791-            name = settings.TEST_DATABASE_TBLSPACE
1792-    except AttributeError:
1793-        pass
1794-    except:
1795-        raise
1796-    return name
1797-
1798-def _test_database_tblspace_tmp(settings):
1799-    name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
1800-    try:
1801-        if settings.TEST_DATABASE_TBLSPACE_TMP:
1802-            name = settings.TEST_DATABASE_TBLSPACE_TMP
1803-    except AttributeError:
1804-        pass
1805-    except:
1806-        raise
1807-    return name
1808+    def _test_database_tblspace_tmp(settings):
1809+        name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
1810+        try:
1811+            if settings.TEST_DATABASE_TBLSPACE_TMP:
1812+                name = settings.TEST_DATABASE_TBLSPACE_TMP
1813+        except AttributeError:
1814+            pass
1815+        except:
1816+            raise
1817+        return name
1818Index: django/db/backends/__init__.py
1819===================================================================
1820--- django/db/backends/__init__.py      (revision 8156)
1821+++ django/db/backends/__init__.py      (working copy)
1822@@ -325,3 +325,25 @@
1823         """
1824         return self.year_lookup_bounds(value)
1825 
1826+class BaseDatabaseCreation(object):
1827+    """
1828+    This class encapsulates all backend-specific differences that pertain to
1829+    database *creation*, such as the column types to use for particular Django
1830+    Fields.
1831+    """
1832+    data_types = {}
1833+
1834+class BaseDatabaseIntrospection(object):
1835+    """
1836+    This class encapsulates all backend-specific introspection utilities
1837+    """
1838+    data_types_reverse = {}
1839+
1840+class BaseDatabaseClient(object):
1841+    """
1842+    This class encapsualtes all backend-specific methods for opening a
1843+    client shell
1844+    """
1845+    def runshell():
1846+           raise NotImplementedError()
1847+       
1848\ No newline at end of file
1849Index: django/db/backends/postgresql_psycopg2/base.py
1850===================================================================
1851--- django/db/backends/postgresql_psycopg2/base.py      (revision 8156)
1852+++ django/db/backends/postgresql_psycopg2/base.py      (working copy)
1853@@ -6,6 +6,10 @@
1854 
1855 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures
1856 from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
1857+from django.db.backends.postgresql.client import DatabaseClient
1858+from django.db.backends.postgresql.creation import DatabaseCreation
1859+from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
1860+
1861 from django.utils.safestring import SafeUnicode
1862 try:
1863     import psycopg2 as Database
1864@@ -33,6 +37,10 @@
1865 class DatabaseWrapper(BaseDatabaseWrapper):
1866     features = DatabaseFeatures()
1867     ops = DatabaseOperations()
1868+    client = DatabaseClient()
1869+    creation = DatabaseCreation()
1870+    introspection = DatabaseIntrospection(ops)
1871+
1872     operators = {
1873         'exact': '= %s',
1874         'iexact': 'ILIKE %s',
1875Index: django/db/backends/postgresql_psycopg2/client.py
1876===================================================================
1877--- django/db/backends/postgresql_psycopg2/client.py    (revision 8156)
1878+++ django/db/backends/postgresql_psycopg2/client.py    (working copy)
1879@@ -1 +0,0 @@
1880-from django.db.backends.postgresql.client import *
1881Index: django/db/backends/postgresql_psycopg2/introspection.py
1882===================================================================
1883--- django/db/backends/postgresql_psycopg2/introspection.py     (revision 8156)
1884+++ django/db/backends/postgresql_psycopg2/introspection.py     (working copy)
1885@@ -1,83 +1,21 @@
1886-from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
1887+from django.db.backends.postgresql.introspection import DatabaseIntrospection as PostgresDatabaseIntrospection
1888 
1889-quote_name = DatabaseOperations().quote_name
1890+class DatabaseIntrospection(PostgresDatabaseIntrospection):
1891 
1892-def get_table_list(cursor):
1893-    "Returns a list of table names in the current database."
1894-    cursor.execute("""
1895-        SELECT c.relname
1896-        FROM pg_catalog.pg_class c
1897-        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1898-        WHERE c.relkind IN ('r', 'v', '')
1899-            AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
1900-            AND pg_catalog.pg_table_is_visible(c.oid)""")
1901-    return [row[0] for row in cursor.fetchall()]
1902-
1903-def get_table_description(cursor, table_name):
1904-    "Returns a description of the table, with the DB-API cursor.description interface."
1905-    cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
1906-    return cursor.description
1907-
1908-def get_relations(cursor, table_name):
1909-    """
1910-    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1911-    representing all relationships to the given table. Indexes are 0-based.
1912-    """
1913-    cursor.execute("""
1914-        SELECT con.conkey, con.confkey, c2.relname
1915-        FROM pg_constraint con, pg_class c1, pg_class c2
1916-        WHERE c1.oid = con.conrelid
1917-            AND c2.oid = con.confrelid
1918-            AND c1.relname = %s
1919-            AND con.contype = 'f'""", [table_name])
1920-    relations = {}
1921-    for row in cursor.fetchall():
1922-        # row[0] and row[1] are single-item lists, so grab the single item.
1923-        relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
1924-    return relations
1925-
1926-def get_indexes(cursor, table_name):
1927-    """
1928-    Returns a dictionary of fieldname -> infodict for the given table,
1929-    where each infodict is in the format:
1930-        {'primary_key': boolean representing whether it's the primary key,
1931-         'unique': boolean representing whether it's a unique index}
1932-    """
1933-    # This query retrieves each index on the given table, including the
1934-    # first associated field name
1935-    cursor.execute("""
1936-        SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
1937-        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
1938-            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
1939-        WHERE c.oid = idx.indrelid
1940-            AND idx.indexrelid = c2.oid
1941-            AND attr.attrelid = c.oid
1942-            AND attr.attnum = idx.indkey[0]
1943-            AND c.relname = %s""", [table_name])
1944-    indexes = {}
1945-    for row in cursor.fetchall():
1946-        # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
1947-        # a string of space-separated integers. This designates the field
1948-        # indexes (1-based) of the fields that have indexes on the table.
1949-        # Here, we skip any indexes across multiple fields.
1950-        if ' ' in row[1]:
1951-            continue
1952-        indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
1953-    return indexes
1954-
1955-# Maps type codes to Django Field types.
1956-DATA_TYPES_REVERSE = {
1957-    16: 'BooleanField',
1958-    21: 'SmallIntegerField',
1959-    23: 'IntegerField',
1960-    25: 'TextField',
1961-    701: 'FloatField',
1962-    869: 'IPAddressField',
1963-    1043: 'CharField',
1964-    1082: 'DateField',
1965-    1083: 'TimeField',
1966-    1114: 'DateTimeField',
1967-    1184: 'DateTimeField',
1968-    1266: 'TimeField',
1969-    1700: 'DecimalField',
1970-}
1971+    def get_relations(self, cursor, table_name):
1972+        """
1973+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
1974+        representing all relationships to the given table. Indexes are 0-based.
1975+        """
1976+        cursor.execute("""
1977+            SELECT con.conkey, con.confkey, c2.relname
1978+            FROM pg_constraint con, pg_class c1, pg_class c2
1979+            WHERE c1.oid = con.conrelid
1980+                AND c2.oid = con.confrelid
1981+                AND c1.relname = %s
1982+                AND con.contype = 'f'""", [table_name])
1983+        relations = {}
1984+        for row in cursor.fetchall():
1985+            # row[0] and row[1] are single-item lists, so grab the single item.
1986+            relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
1987+        return relations
1988Index: django/db/backends/postgresql_psycopg2/creation.py
1989===================================================================
1990--- django/db/backends/postgresql_psycopg2/creation.py  (revision 8156)
1991+++ django/db/backends/postgresql_psycopg2/creation.py  (working copy)
1992@@ -1 +0,0 @@
1993-from django.db.backends.postgresql.creation import *
1994Index: django/db/backends/dummy/base.py
1995===================================================================
1996--- django/db/backends/dummy/base.py    (revision 8156)
1997+++ django/db/backends/dummy/base.py    (working copy)
1998@@ -9,6 +9,7 @@
1999 
2000 from django.core.exceptions import ImproperlyConfigured
2001 from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations
2002+from django.db.backends import BaseDatabaseClient, BaseDatabaseCreation, BaseDatabaseIntrospection
2003 
2004 def complain(*args, **kwargs):
2005     raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
2006@@ -25,9 +26,25 @@
2007 class DatabaseOperations(BaseDatabaseOperations):
2008     quote_name = complain
2009 
2010+class DatabaseClient(BaseDatabaseClient):
2011+    runshell = complain
2012+   
2013+class DatabaseCreation(BaseDatabaseCreation):
2014+    pass
2015+   
2016+class DatabaseIntrospection(BaseDatabaseIntrospection):
2017+    get_table_list = complain
2018+    get_table_description = complain
2019+    get_relations = complain
2020+    get_indexes = complain
2021+   
2022 class DatabaseWrapper(object):
2023     features = BaseDatabaseFeatures()
2024     ops = DatabaseOperations()
2025+    client = DatabaseClient()
2026+    creation = DatabaseCreation()
2027+    introspection = DatabaseIntrospection()
2028+
2029     operators = {}
2030     cursor = complain
2031     _commit = complain
2032Index: django/db/backends/dummy/client.py
2033===================================================================
2034--- django/db/backends/dummy/client.py  (revision 8156)
2035+++ django/db/backends/dummy/client.py  (working copy)
2036@@ -1,3 +0,0 @@
2037-from django.db.backends.dummy.base import complain
2038-
2039-runshell = complain
2040Index: django/db/backends/dummy/introspection.py
2041===================================================================
2042--- django/db/backends/dummy/introspection.py   (revision 8156)
2043+++ django/db/backends/dummy/introspection.py   (working copy)
2044@@ -1,8 +0,0 @@
2045-from django.db.backends.dummy.base import complain
2046-
2047-get_table_list = complain
2048-get_table_description = complain
2049-get_relations = complain
2050-get_indexes = complain
2051-
2052-DATA_TYPES_REVERSE = {}
2053Index: django/db/backends/dummy/creation.py
2054===================================================================
2055--- django/db/backends/dummy/creation.py        (revision 8156)
2056+++ django/db/backends/dummy/creation.py        (working copy)
2057@@ -1 +0,0 @@
2058-DATA_TYPES = {}
2059Index: django/db/backends/creation.py
2060===================================================================
2061--- django/db/backends/creation.py      (revision 8156)
2062+++ django/db/backends/creation.py      (working copy)
2063@@ -1,7 +0,0 @@
2064-class BaseCreation(object):
2065-    """
2066-    This class encapsulates all backend-specific differences that pertain to
2067-    database *creation*, such as the column types to use for particular Django
2068-    Fields.
2069-    """
2070-    pass
2071Index: django/core/management/commands/inspectdb.py
2072===================================================================
2073--- django/core/management/commands/inspectdb.py        (revision 8156)
2074+++ django/core/management/commands/inspectdb.py        (working copy)
2075@@ -13,11 +13,9 @@
2076             raise CommandError("Database inspection isn't supported for the currently selected database backend.")
2077 
2078     def handle_inspection(self):
2079-        from django.db import connection, get_introspection_module
2080+        from django.db import connection
2081         import keyword
2082 
2083-        introspection_module = get_introspection_module()
2084-
2085         table2model = lambda table_name: table_name.title().replace('_', '')
2086 
2087         cursor = connection.cursor()
2088@@ -32,17 +30,17 @@
2089         yield ''
2090         yield 'from django.db import models'
2091         yield ''
2092-        for table_name in introspection_module.get_table_list(cursor):
2093+        for table_name in connection.introspection.get_table_list(cursor):
2094             yield 'class %s(models.Model):' % table2model(table_name)
2095             try:
2096-                relations = introspection_module.get_relations(cursor, table_name)
2097+                relations = connection.introspection.get_relations(cursor, table_name)
2098             except NotImplementedError:
2099                 relations = {}
2100             try:
2101-                indexes = introspection_module.get_indexes(cursor, table_name)
2102+                indexes = connection.introspection.get_indexes(cursor, table_name)
2103             except NotImplementedError:
2104                 indexes = {}
2105-            for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
2106+            for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
2107                 att_name = row[0].lower()
2108                 comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
2109                 extra_params = {}  # Holds Field parameters such as 'db_column'.
2110@@ -65,7 +63,7 @@
2111                         extra_params['db_column'] = att_name
2112                 else:
2113                     try:
2114-                        field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
2115+                        field_type = connection.introspection.data_types_reverse[row[1]]
2116                     except KeyError:
2117                         field_type = 'TextField'
2118                         comment_notes.append('This field type is a guess.')
2119Index: django/core/management/commands/dbshell.py
2120===================================================================
2121--- django/core/management/commands/dbshell.py  (revision 8156)
2122+++ django/core/management/commands/dbshell.py  (working copy)
2123@@ -6,5 +6,5 @@
2124     requires_model_validation = False
2125 
2126     def handle_noargs(self, **options):
2127-        from django.db import runshell
2128-        runshell()
2129+        from django.db import connection
2130+        connection.client.runshell()
2131Index: django/core/management/sql.py
2132===================================================================
2133--- django/core/management/sql.py       (revision 8157)
2134+++ django/core/management/sql.py       (working copy)
2135@@ -9,9 +9,9 @@
2136 
2137 def table_names():
2138     "Returns a list of all table names that exist in the database."
2139-    from django.db import connection, get_introspection_module
2140+    from django.db import connection
2141     cursor = connection.cursor()
2142-    return set(get_introspection_module().get_table_list(cursor))
2143+    return set(connection.introspection.get_table_list(cursor))
2144 
2145 def django_table_names(only_existing=False):
2146     """