Code

Ticket #6148: 6148-r16443-2.diff

File 6148-r16443-2.diff, 78.5 KB (added by Demetrius Cassidy <dcassidy36@…>, 3 years ago)
Line 
1Index: docs/topics/db/models.txt
2===================================================================
3--- docs/topics/db/models.txt   (revision 16443)
4+++ docs/topics/db/models.txt   (working copy)
5@@ -626,7 +626,8 @@
6             verbose_name_plural = "oxen"
7 
8 Model metadata is "anything that's not a field", such as ordering options
9-(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`), or
10+(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`),
11+custom schema for the tables (:attr:`~Options.db_schema`), or
12 human-readable singular and plural names (:attr:`~Options.verbose_name` and
13 :attr:`~Options.verbose_name_plural`). None are required, and adding ``class
14 Meta`` to a model is completely optional.
15Index: docs/ref/models/options.txt
16===================================================================
17--- docs/ref/models/options.txt (revision 16443)
18+++ docs/ref/models/options.txt (working copy)
19@@ -61,6 +61,23 @@
20 aren't allowed in Python variable names -- notably, the hyphen -- that's OK.
21 Django quotes column and table names behind the scenes.
22 
23+.. _db_schema:
24+
25+``db_schema``
26+-------------
27+
28+.. attribute:: Options.db_schema
29+
30+.. versionadded:: 1.3
31+
32+The name of the database schema to use for the model. If the backend
33+doesn't support multiple schemas, this option is ignored.
34+
35+If this is used then Django will prefix the model table names with the schema
36+name. For example with MySQL Django would use ``db_schema + '.' + db_table``.
37+Be aware that PostgreSQL supports different schemas within the database.
38+MySQL solves the same thing by treating it as just another database.
39+
40 ``db_tablespace``
41 -----------------
42 
43Index: docs/ref/settings.txt
44===================================================================
45--- docs/ref/settings.txt       (revision 16443)
46+++ docs/ref/settings.txt       (working copy)
47@@ -510,6 +510,23 @@
48 The port to use when connecting to the database. An empty string means the
49 default port. Not used with SQLite.
50 
51+.. setting:: SCHEMA
52+
53+SCHEMA
54+~~~~~~
55+
56+.. versionadded:: 1.3
57+
58+Default: ``''`` (Empty string)
59+
60+The name of the database schema to use for models. If the backend
61+doesn't support multiple schemas, this option is ignored. An empty
62+string means the default schema.
63+
64+If this is used then Django will prefix any table names with the schema name.
65+The schema can be overriden on a per-model basis, for details see
66+:ref:`db_schema`.
67+
68 .. setting:: USER
69 
70 USER
71Index: django/db/models/sql/compiler.py
72===================================================================
73--- django/db/models/sql/compiler.py    (revision 16443)
74+++ django/db/models/sql/compiler.py    (working copy)
75@@ -23,7 +23,7 @@
76         might not have all the pieces in place at that time.
77         """
78         if not self.query.tables:
79-            self.query.join((None, self.query.model._meta.db_table, None, None))
80+            self.query.join((None, self.query.model._meta.qualified_name, None, None))
81         if (not self.query.select and self.query.default_cols and not
82                 self.query.included_inherited_models):
83             self.query.setup_inherited_models()
84@@ -261,7 +261,7 @@
85                         alias = start_alias
86                     else:
87                         link_field = opts.get_ancestor_link(model)
88-                        alias = self.query.join((start_alias, model._meta.db_table,
89+                        alias = self.query.join((start_alias, model._meta.qualified_name,
90                                 link_field.column, model._meta.pk.column))
91                     seen[model] = alias
92             else:
93@@ -462,7 +462,7 @@
94                 result.append('%s%s%s' % (connector, qn(name), alias_str))
95             first = False
96         for t in self.query.extra_tables:
97-            alias, unused = self.query.table_alias(t)
98+            alias, unused = self.query.table_alias(qn(t))
99             # Only add the alias if it's not already present (the table_alias()
100             # calls increments the refcount, so an alias refcount of one means
101             # this is the only reference.
102@@ -550,7 +550,7 @@
103             # what "used" specifies).
104             avoid = avoid_set.copy()
105             dupe_set = orig_dupe_set.copy()
106-            table = f.rel.to._meta.db_table
107+            table = f.rel.to._meta.qualified_name
108             promote = nullable or f.null
109             if model:
110                 int_opts = opts
111@@ -571,7 +571,7 @@
112                                 ()))
113                         dupe_set.add((opts, lhs_col))
114                     int_opts = int_model._meta
115-                    alias = self.query.join((alias, int_opts.db_table, lhs_col,
116+                    alias = self.query.join((alias, int_opts.qualified_name, lhs_col,
117                             int_opts.pk.column), exclusions=used,
118                             promote=promote)
119                     alias_chain.append(alias)
120@@ -623,7 +623,7 @@
121                 # what "used" specifies).
122                 avoid = avoid_set.copy()
123                 dupe_set = orig_dupe_set.copy()
124-                table = model._meta.db_table
125+                table = model._meta.qualified_name
126 
127                 int_opts = opts
128                 alias = root_alias
129@@ -646,7 +646,7 @@
130                             dupe_set.add((opts, lhs_col))
131                         int_opts = int_model._meta
132                         alias = self.query.join(
133-                            (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
134+                            (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column),
135                             exclusions=used, promote=True, reuse=used
136                         )
137                         alias_chain.append(alias)
138@@ -793,13 +793,13 @@
139         # going to be column names (so we can avoid the extra overhead).
140         qn = self.connection.ops.quote_name
141         opts = self.query.model._meta
142-        result = ['INSERT INTO %s' % qn(opts.db_table)]
143+        result = ['INSERT INTO %s' % opts.qualified_name]
144         result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
145         values = [self.placeholder(*v) for v in self.query.values]
146         result.append('VALUES (%s)' % ', '.join(values))
147         params = self.query.params
148         if self.return_id and self.connection.features.can_return_id_from_insert:
149-            col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
150+            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
151             r_fmt, r_params = self.connection.ops.return_insert_id()
152             result.append(r_fmt % col)
153             params = params + r_params
154@@ -813,7 +813,8 @@
155         if self.connection.features.can_return_id_from_insert:
156             return self.connection.ops.fetch_returned_insert_id(cursor)
157         return self.connection.ops.last_insert_id(cursor,
158-                self.query.model._meta.db_table, self.query.model._meta.pk.column)
159+                self.query.model._meta.db_schema, self.query.model._meta.db_table,
160+                self.query.model._meta.pk.column)
161 
162 
163 class SQLDeleteCompiler(SQLCompiler):
164Index: django/db/models/sql/query.py
165===================================================================
166--- django/db/models/sql/query.py       (revision 16443)
167+++ django/db/models/sql/query.py       (working copy)
168@@ -620,7 +620,7 @@
169         Callback used by deferred_to_columns(). The "target" parameter should
170         be a set instance.
171         """
172-        table = model._meta.db_table
173+        table = model._meta.qualified_name
174         if table not in target:
175             target[table] = set()
176         for field in fields:
177@@ -809,7 +809,8 @@
178             alias = self.tables[0]
179             self.ref_alias(alias)
180         else:
181-            alias = self.join((None, self.model._meta.db_table, None, None))
182+            alias = self.join((None, self.model._meta.qualified_name,
183+                               None, None))
184         return alias
185 
186     def count_active_tables(self):
187@@ -922,7 +923,8 @@
188                     seen[model] = root_alias
189                 else:
190                     link_field = opts.get_ancestor_link(model)
191-                    seen[model] = self.join((root_alias, model._meta.db_table,
192+                    seen[model] = self.join((root_alias,
193+                            model._meta.qualified_name,
194                             link_field.column, model._meta.pk.column))
195         self.included_inherited_models = seen
196 
197@@ -1269,8 +1271,9 @@
198                                     (id(opts), lhs_col), ()))
199                             dupe_set.add((opts, lhs_col))
200                         opts = int_model._meta
201-                        alias = self.join((alias, opts.db_table, lhs_col,
202-                                opts.pk.column), exclusions=exclusions)
203+                        alias = self.join((alias, opts.qualified_name,
204+                                           lhs_col, opts.pk.column),
205+                                          exclusions=exclusions)
206                         joins.append(alias)
207                         exclusions.add(alias)
208                         for (dupe_opts, dupe_col) in dupe_set:
209@@ -1295,12 +1298,12 @@
210                         (table1, from_col1, to_col1, table2, from_col2,
211                                 to_col2, opts, target) = cached_data
212                     else:
213-                        table1 = field.m2m_db_table()
214+                        table1 = field.m2m_qualified_name()
215                         from_col1 = opts.get_field_by_name(
216                             field.m2m_target_field_name())[0].column
217                         to_col1 = field.m2m_column_name()
218                         opts = field.rel.to._meta
219-                        table2 = opts.db_table
220+                        table2 = opts.qualified_name
221                         from_col2 = field.m2m_reverse_name()
222                         to_col2 = opts.get_field_by_name(
223                             field.m2m_reverse_target_field_name())[0].column
224@@ -1328,7 +1331,7 @@
225                     else:
226                         opts = field.rel.to._meta
227                         target = field.rel.get_related_field()
228-                        table = opts.db_table
229+                        table = opts.qualified_name
230                         from_col = field.column
231                         to_col = target.column
232                         orig_opts._join_cache[name] = (table, from_col, to_col,
233@@ -1350,12 +1353,12 @@
234                         (table1, from_col1, to_col1, table2, from_col2,
235                                 to_col2, opts, target) = cached_data
236                     else:
237-                        table1 = field.m2m_db_table()
238+                        table1 = field.m2m_qualified_name()
239                         from_col1 = opts.get_field_by_name(
240                             field.m2m_reverse_target_field_name())[0].column
241                         to_col1 = field.m2m_reverse_name()
242                         opts = orig_field.opts
243-                        table2 = opts.db_table
244+                        table2 = opts.qualified_name
245                         from_col2 = field.m2m_column_name()
246                         to_col2 = opts.get_field_by_name(
247                             field.m2m_target_field_name())[0].column
248@@ -1379,7 +1382,7 @@
249                         local_field = opts.get_field_by_name(
250                                 field.rel.field_name)[0]
251                         opts = orig_field.opts
252-                        table = opts.db_table
253+                        table = opts.qualified_name
254                         from_col = local_field.column
255                         to_col = field.column
256                         # In case of a recursive FK, use the to_field for
257@@ -1649,7 +1652,8 @@
258         else:
259             opts = self.model._meta
260             if not self.select:
261-                count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
262+                count = self.aggregates_module.Count((self.join((None,
263+                           opts.qualified_name, None, None)), opts.pk.column),
264                                          is_summary=True, distinct=True)
265             else:
266                 # Because of SQL portability issues, multi-column, distinct
267Index: django/db/models/sql/subqueries.py
268===================================================================
269--- django/db/models/sql/subqueries.py  (revision 16443)
270+++ django/db/models/sql/subqueries.py  (working copy)
271@@ -41,7 +41,7 @@
272             where = self.where_class()
273             where.add((Constraint(None, field.column, field), 'in',
274                     pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
275-            self.do_query(self.model._meta.db_table, where, using=using)
276+            self.do_query(self.model._meta.qualified_name, where, using=using)
277 
278 class UpdateQuery(Query):
279     """
280Index: django/db/models/options.py
281===================================================================
282--- django/db/models/options.py (revision 16443)
283+++ django/db/models/options.py (working copy)
284@@ -17,7 +17,7 @@
285 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
286                  'unique_together', 'permissions', 'get_latest_by',
287                  'order_with_respect_to', 'app_label', 'db_tablespace',
288-                 'abstract', 'managed', 'proxy', 'auto_created')
289+                 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema')
290 
291 class Options(object):
292     def __init__(self, meta, app_label=None):
293@@ -26,6 +26,9 @@
294         self.module_name, self.verbose_name = None, None
295         self.verbose_name_plural = None
296         self.db_table = ''
297+        #self.db_schema = settings.DATABASE_SCHEMA
298+        self.db_schema = None
299+        self.qualified_name = ''
300         self.ordering = []
301         self.unique_together =  []
302         self.permissions =  []
303@@ -55,8 +58,9 @@
304         self.related_fkey_lookups = []
305 
306     def contribute_to_class(self, cls, name):
307-        from django.db import connection
308+        from django.db import connections, router
309         from django.db.backends.util import truncate_name
310+        conn = connections[router.db_for_read(cls)]
311 
312         cls._meta = self
313         self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
314@@ -64,6 +68,7 @@
315         self.object_name = cls.__name__
316         self.module_name = self.object_name.lower()
317         self.verbose_name = get_verbose_name(self.object_name)
318+        self.db_schema = conn.settings_dict['SCHEMA']
319 
320         # Next, apply any overridden values from 'class Meta'.
321         if self.meta:
322@@ -103,8 +108,14 @@
323         # If the db_table wasn't provided, use the app_label + module_name.
324         if not self.db_table:
325             self.db_table = "%s_%s" % (self.app_label, self.module_name)
326-            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
327+            self.db_table = truncate_name(self.db_table, conn.ops.max_name_length())
328 
329+        # Construct qualified table name.
330+        self.qualified_name = conn.ops.prep_db_table(self.db_schema,
331+                                                           self.db_table)
332+        if self.qualified_name == conn.ops.quote_name(self.db_table):
333+            # If unchanged, the backend doesn't support schemas.
334+            self.db_schema = ''
335     def _prepare(self, model):
336         if self.order_with_respect_to:
337             self.order_with_respect_to = self.get_field(self.order_with_respect_to)
338@@ -184,6 +195,8 @@
339         self.pk = target._meta.pk
340         self.proxy_for_model = target
341         self.db_table = target._meta.db_table
342+        self.db_schema = target._meta.db_schema
343+        self.qualified_name = target._meta.qualified_name
344 
345     def __repr__(self):
346         return '<Options for %s>' % self.object_name
347Index: django/db/models/fields/related.py
348===================================================================
349--- django/db/models/fields/related.py  (revision 16443)
350+++ django/db/models/fields/related.py  (working copy)
351@@ -892,7 +892,7 @@
352         if isinstance(self.rel.to, basestring):
353             target = self.rel.to
354         else:
355-            target = self.rel.to._meta.db_table
356+            target = self.rel.to._meta.qualified_name
357         cls._meta.duplicate_targets[self.column] = (target, "o2m")
358 
359     def contribute_to_related_class(self, cls, related):
360@@ -983,6 +983,7 @@
361         to = to.lower()
362     meta = type('Meta', (object,), {
363         'db_table': field._get_m2m_db_table(klass._meta),
364+        'db_schema': field._get_m2m_db_schema(klass._meta),
365         'managed': managed,
366         'auto_created': klass,
367         'app_label': klass._meta.app_label,
368@@ -1014,6 +1015,7 @@
369             through=kwargs.pop('through', None))
370 
371         self.db_table = kwargs.pop('db_table', None)
372+        self.db_schema = kwargs.pop('db_schema', '')
373         if kwargs['rel'].through is not None:
374             assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
375 
376@@ -1035,6 +1037,18 @@
377             return util.truncate_name('%s_%s' % (opts.db_table, self.name),
378                                       connection.ops.max_name_length())
379 
380+    def _get_m2m_db_schema(self, opts):
381+        "Function that can be curried to provide the m2m schema name for this relation"
382+        if self.rel.through is not None and self.rel.through._meta.db_schema:
383+            return self.rel.through._meta.db_schema
384+        return self.db_schema
385+
386+    def _get_m2m_qualified_name(self, opts):
387+        "Function that can be curried to provide the qualified m2m table name for this relation"
388+        schema = self._get_m2m_db_schema(opts)
389+        table = self._get_m2m_db_table(opts)
390+        return connection.ops.prep_db_table(schema, table)
391+
392     def _get_m2m_attr(self, related, attr):
393         "Function that can be curried to provide the source accessor or DB column name for the m2m table"
394         cache_attr = '_m2m_%s_cache' % attr
395@@ -1105,6 +1119,9 @@
396 
397         # Set up the accessor for the m2m table name for the relation
398         self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
399+        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
400+        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
401+                                        cls._meta)
402 
403         # Populate some necessary rel arguments so that cross-app relations
404         # work correctly.
405@@ -1116,7 +1133,7 @@
406         if isinstance(self.rel.to, basestring):
407             target = self.rel.to
408         else:
409-            target = self.rel.to._meta.db_table
410+            target = self.rel.to._meta.qualified_name
411         cls._meta.duplicate_targets[self.column] = (target, "m2m")
412 
413     def contribute_to_related_class(self, cls, related):
414Index: django/db/__init__.py
415===================================================================
416--- django/db/__init__.py       (revision 16443)
417+++ django/db/__init__.py       (working copy)
418@@ -7,7 +7,7 @@
419 __all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
420     'IntegrityError', 'DEFAULT_DB_ALIAS')
421 
422-
423+       
424 if DEFAULT_DB_ALIAS not in settings.DATABASES:
425     raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
426 
427Index: django/db/utils.py
428===================================================================
429--- django/db/utils.py  (revision 16443)
430+++ django/db/utils.py  (working copy)
431@@ -67,7 +67,8 @@
432             conn['ENGINE'] = 'django.db.backends.dummy'
433         conn.setdefault('OPTIONS', {})
434         conn.setdefault('TIME_ZONE', settings.TIME_ZONE)
435-        for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
436+
437+        for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT', 'SCHEMA'):
438             conn.setdefault(setting, '')
439         for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:
440             conn.setdefault(setting, None)
441Index: django/db/backends/postgresql/base.py
442===================================================================
443--- django/db/backends/postgresql/base.py       (revision 13366)
444+++ django/db/backends/postgresql/base.py       (working copy)
445@@ -80,6 +80,7 @@
446 
447 class DatabaseFeatures(BaseDatabaseFeatures):
448     uses_savepoints = True
449+    default_schema_name = u'public'
450 
451 class DatabaseWrapper(BaseDatabaseWrapper):
452     operators = {
453Index: django/db/backends/postgresql/introspection.py
454===================================================================
455--- django/db/backends/postgresql/introspection.py      (revision 13366)
456+++ django/db/backends/postgresql/introspection.py      (working copy)
457@@ -31,6 +31,24 @@
458                 AND pg_catalog.pg_table_is_visible(c.oid)""")
459         return [row[0] for row in cursor.fetchall()]
460 
461+    def get_schema_list(self, cursor):
462+        cursor.execute("""
463+            SELECT DISTINCT n.nspname
464+            FROM pg_catalog.pg_class c
465+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
466+            WHERE c.relkind IN ('r', 'v', '')
467+            AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')""")
468+        return [row[0] for row in cursor.fetchall()]
469+
470+    def get_schema_table_list(self, cursor, schema):
471+        cursor.execute("""
472+            SELECT c.relname
473+            FROM pg_catalog.pg_class c
474+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
475+            WHERE c.relkind IN ('r', 'v', '')
476+                AND n.nspname = '%s'""" % schema)
477+        return [row[0] for row in cursor.fetchall()]
478+
479     def get_table_description(self, cursor, table_name):
480         "Returns a description of the table, with the DB-API cursor.description interface."
481         cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
482Index: django/db/backends/postgresql/operations.py
483===================================================================
484--- django/db/backends/postgresql/operations.py (revision 13366)
485+++ django/db/backends/postgresql/operations.py (working copy)
486@@ -53,9 +53,10 @@
487             return 'HOST(%s)'
488         return '%s'
489 
490-    def last_insert_id(self, cursor, table_name, pk_name):
491+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
492         # Use pg_get_serial_sequence to get the underlying sequence name
493         # from the table name and column name (available since PostgreSQL 8)
494+        table_name = self.prep_db_table(schema_name, table_name)
495         cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % (table_name, pk_name))
496         return cursor.fetchone()[0]
497 
498@@ -67,8 +68,17 @@
499             return name # Quoting once is enough.
500         return '"%s"' % name
501 
502+    def prep_db_table(self, db_schema, db_table):
503+        qn = self.quote_name
504+        if db_schema:
505+            return "%s.%s" % (qn(db_schema), qn(db_table))
506+        else:
507+            return qn(db_table)
508+
509     def sql_flush(self, style, tables, sequences):
510         if tables:
511+            qnames = [self.prep_db_table(schema, table)
512+                      for (schema, table) in tables]
513             if self.postgres_version[0:2] >= (8,1):
514                 # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
515                 # in order to be able to truncate tables referenced by a foreign
516@@ -76,7 +86,7 @@
517                 # statement.
518                 sql = ['%s %s;' % \
519                     (style.SQL_KEYWORD('TRUNCATE'),
520-                     style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
521+                     style.SQL_FIELD(', '.join(qnames))
522                 )]
523             else:
524                 # Older versions of Postgres can't do TRUNCATE in a single call, so
525@@ -84,18 +94,20 @@
526                 sql = ['%s %s %s;' % \
527                         (style.SQL_KEYWORD('DELETE'),
528                          style.SQL_KEYWORD('FROM'),
529-                         style.SQL_FIELD(self.quote_name(table))
530-                         ) for table in tables]
531+                         style.SQL_FIELD(qname)
532+                         ) for qname in qnames]
533 
534             # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
535             # to reset sequence indices
536             for sequence_info in sequences:
537+                schema_name = sequence_info['schema']
538                 table_name = sequence_info['table']
539                 column_name = sequence_info['column']
540                 if not (column_name and len(column_name) > 0):
541                     # This will be the case if it's an m2m using an autogenerated
542                     # intermediate table (see BaseDatabaseIntrospection.sequence_list)
543                     column_name = 'id'
544+                table_name = self.prep_db_table(schema_name, table_name)
545                 sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \
546                     (style.SQL_KEYWORD('SELECT'),
547                     style.SQL_TABLE(table_name),
548@@ -118,27 +130,29 @@
549 
550             for f in model._meta.local_fields:
551                 if isinstance(f, models.AutoField):
552+                    table_name = self.prep_db_table(model._meta.db_schema, model._meta.db_table) # XXX: generic schemas support.
553                     output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
554                         (style.SQL_KEYWORD('SELECT'),
555-                        style.SQL_TABLE(model._meta.db_table),
556-                        style.SQL_FIELD(f.column),
557+                        style.SQL_TABLE(table_name),
558+                        style.SQL_FIELD(f.column), # XXX: Do we need qn() here?
559                         style.SQL_FIELD(qn(f.column)),
560                         style.SQL_FIELD(qn(f.column)),
561                         style.SQL_KEYWORD('IS NOT'),
562                         style.SQL_KEYWORD('FROM'),
563-                        style.SQL_TABLE(qn(model._meta.db_table))))
564+                        style.SQL_TABLE(model._meta.qualified_name)))
565                     break # Only one AutoField is allowed per model, so don't bother continuing.
566             for f in model._meta.many_to_many:
567                 if not f.rel.through:
568+                    table_name = self.prep_db_table(f.m2m_db_schema(), f.m2m_db_table()) # XXX: Do we need qn(f.m2m_db_table()) here? / XXX: generic schemas support.
569                     output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
570                         (style.SQL_KEYWORD('SELECT'),
571-                        style.SQL_TABLE(model._meta.db_table),
572-                        style.SQL_FIELD('id'),
573+                        style.SQL_TABLE(table_name),
574+                        style.SQL_FIELD('id'), # XXX: Do we need qn() here?
575                         style.SQL_FIELD(qn('id')),
576                         style.SQL_FIELD(qn('id')),
577                         style.SQL_KEYWORD('IS NOT'),
578                         style.SQL_KEYWORD('FROM'),
579-                        style.SQL_TABLE(qn(f.m2m_db_table()))))
580+                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
581         return output
582 
583     def savepoint_create_sql(self, sid):
584Index: django/db/backends/postgresql/creation.py
585===================================================================
586--- django/db/backends/postgresql/creation.py   (revision 13366)
587+++ django/db/backends/postgresql/creation.py   (working copy)
588@@ -51,10 +51,12 @@
589                 tablespace_sql = ''
590 
591             def get_index_sql(index_name, opclass=''):
592+                index_name = truncate_name(index_name, self.connection.ops.max_name_length())
593+                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
594                 return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
595-                        style.SQL_TABLE(qn(truncate_name(index_name,self.connection.ops.max_name_length()))) + ' ' +
596+                        style.SQL_TABLE(index_name) + ' ' +
597                         style.SQL_KEYWORD('ON') + ' ' +
598-                        style.SQL_TABLE(qn(db_table)) + ' ' +
599+                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
600                         "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
601                         "%s;" % tablespace_sql)
602 
603Index: django/db/backends/sqlite3/base.py
604===================================================================
605--- django/db/backends/sqlite3/base.py  (revision 16443)
606+++ django/db/backends/sqlite3/base.py  (working copy)
607@@ -123,7 +123,7 @@
608                 (style.SQL_KEYWORD('DELETE'),
609                  style.SQL_KEYWORD('FROM'),
610                  style.SQL_FIELD(self.quote_name(table))
611-                 ) for table in tables]
612+                 ) for (_, table) in tables]
613         # Note: No requirement for reset of auto-incremented indices (cf. other
614         # sql_flush() implementations). Just return SQL at this point
615         return sql
616Index: django/db/backends/sqlite3/creation.py
617===================================================================
618--- django/db/backends/sqlite3/creation.py      (revision 16443)
619+++ django/db/backends/sqlite3/creation.py      (working copy)
620@@ -45,7 +45,7 @@
621             return test_database_name
622         return ':memory:'
623 
624-    def _create_test_db(self, verbosity, autoclobber):
625+    def _create_test_db(self, verbosity, autoclobber, schemas):
626         test_database_name = self._get_test_db_name()
627         if test_database_name != ':memory:':
628             # Erase the old test database
629Index: django/db/backends/mysql/base.py
630===================================================================
631--- django/db/backends/mysql/base.py    (revision 16443)
632+++ django/db/backends/mysql/base.py    (working copy)
633@@ -206,6 +206,16 @@
634             return name # Quoting once is enough.
635         return "`%s`" % name
636 
637+    def prep_db_table(self, db_schema, db_table):
638+        qn = self.quote_name
639+        if db_schema:
640+            return "%s.%s" % (qn(db_schema), qn(db_table))
641+        else:
642+            return qn(db_table)
643+
644+    def prep_db_index(self, db_schema, db_index):
645+        return self.prep_db_table(db_schema, db_index)
646+
647     def random_function_sql(self):
648         return 'RAND()'
649 
650@@ -215,19 +225,22 @@
651         # to clear all tables of all data
652         if tables:
653             sql = ['SET FOREIGN_KEY_CHECKS = 0;']
654-            for table in tables:
655-                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
656+            for (schema, table) in tables:
657+                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
658             sql.append('SET FOREIGN_KEY_CHECKS = 1;')
659 
660             # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
661             # to reset sequence indices
662-            sql.extend(["%s %s %s %s %s;" % \
663-                (style.SQL_KEYWORD('ALTER'),
664-                 style.SQL_KEYWORD('TABLE'),
665-                 style.SQL_TABLE(self.quote_name(sequence['table'])),
666-                 style.SQL_KEYWORD('AUTO_INCREMENT'),
667-                 style.SQL_FIELD('= 1'),
668-                ) for sequence in sequences])
669+            for sequence_info in sequences:
670+                schema_name = sequence_info['schema']
671+                table_name = self.prep_db_table(schema_name, sequence_info['table'])
672+                sql.append("%s %s %s %s %s;" % \
673+                           (style.SQL_KEYWORD('ALTER'),
674+                            style.SQL_KEYWORD('TABLE'),
675+                            style.SQL_TABLE(table_name),
676+                            style.SQL_KEYWORD('AUTO_INCREMENT'),
677+                            style.SQL_FIELD('= 1'),
678+                            ))
679             return sql
680         else:
681             return []
682Index: django/db/backends/mysql/introspection.py
683===================================================================
684--- django/db/backends/mysql/introspection.py   (revision 16443)
685+++ django/db/backends/mysql/introspection.py   (working copy)
686@@ -33,6 +33,14 @@
687         cursor.execute("SHOW TABLES")
688         return [row[0] for row in cursor.fetchall()]
689 
690+    def get_schema_list(self, cursor):
691+        cursor.execute("SHOW SCHEMAS")
692+        return [row[0] for row in cursor.fetchall()]
693+
694+    def get_schema_table_list(self, cursor, schema):
695+        cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema))
696+        return [schema + "." + row[0] for row in cursor.fetchall()]
697+
698     def get_table_description(self, cursor, table_name):
699         "Returns a description of the table, with the DB-API cursor.description interface."
700         cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
701Index: django/db/backends/mysql/creation.py
702===================================================================
703--- django/db/backends/mysql/creation.py        (revision 16443)
704+++ django/db/backends/mysql/creation.py        (working copy)
705@@ -64,3 +64,23 @@
706                 field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
707             ]
708         return table_output, deferred
709+
710+    def default_schema(self):
711+        return settings.DATABASE_NAME
712+
713+    def sql_create_schema(self, schema, style):
714+        """
715+        Returns the SQL required to create a single schema.
716+        In MySQL schemas are synonymous to databases
717+        """
718+        qn = self.connection.ops.quote_name
719+        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
720+        return output
721+
722+    def sql_destroy_schema(self, schema, style):
723+        """"
724+        Returns the SQL required to destroy a single schema.
725+        """
726+        qn = self.connection.ops.quote_name
727+        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
728+        return output
729Index: django/db/backends/oracle/base.py
730===================================================================
731--- django/db/backends/oracle/base.py   (revision 16443)
732+++ django/db/backends/oracle/base.py   (working copy)
733@@ -82,12 +82,14 @@
734 class DatabaseOperations(BaseDatabaseOperations):
735     compiler_module = "django.db.backends.oracle.compiler"
736 
737-    def autoinc_sql(self, table, column):
738+    def autoinc_sql(self, schema, table, column):
739         # To simulate auto-incrementing primary keys in Oracle, we have to
740         # create a sequence and a trigger.
741         sq_name = self._get_sequence_name(table)
742         tr_name = self._get_trigger_name(table)
743-        tbl_name = self.quote_name(table)
744+        tbl_name = self.prep_db_table(schema, table)
745+        sq_qname = self.prep_db_table(schema, sq_name)
746+        tr_qname = self.prep_db_table(schema, tr_name)
747         col_name = self.quote_name(column)
748         sequence_sql = """
749 DECLARE
750@@ -96,17 +98,17 @@
751     SELECT COUNT(*) INTO i FROM USER_CATALOG
752         WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
753     IF i = 0 THEN
754-        EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
755+        EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s';
756     END IF;
757 END;
758 /""" % locals()
759         trigger_sql = """
760-CREATE OR REPLACE TRIGGER "%(tr_name)s"
761+CREATE OR REPLACE TRIGGER %(tr_qname)s
762 BEFORE INSERT ON %(tbl_name)s
763 FOR EACH ROW
764 WHEN (new.%(col_name)s IS NULL)
765     BEGIN
766-        SELECT "%(sq_name)s".nextval
767+        SELECT %(sq_qname)s.nextval
768         INTO :new.%(col_name)s FROM dual;
769     END;
770 /""" % locals()
771@@ -191,8 +193,9 @@
772     def deferrable_sql(self):
773         return " DEFERRABLE INITIALLY DEFERRED"
774 
775-    def drop_sequence_sql(self, table):
776-        return "DROP SEQUENCE %s;" % self.quote_name(self._get_sequence_name(table))
777+    def drop_sequence_sql(self, schema, table):
778+        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
779+        return "DROP SEQUENCE %s;" % sequence_name
780 
781     def fetch_returned_insert_id(self, cursor):
782         return long(cursor._insert_id_var.getvalue())
783@@ -203,16 +206,15 @@
784         else:
785             return "%s"
786 
787+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
788+        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
789+        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
790+
791     def last_executed_query(self, cursor, sql, params):
792         # http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
793         # The DB API definition does not define this attribute.
794         return cursor.statement
795 
796-    def last_insert_id(self, cursor, table_name, pk_name):
797-        sq_name = self._get_sequence_name(table_name)
798-        cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
799-        return cursor.fetchone()[0]
800-
801     def lookup_cast(self, lookup_type):
802         if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
803             return "UPPER(%s)"
804@@ -224,6 +226,16 @@
805     def max_name_length(self):
806         return 30
807 
808+    def prep_db_table(self, db_schema, db_table):
809+        qn = self.quote_name
810+        if db_schema:
811+            return "%s.%s" % (qn(db_schema), qn(db_table))
812+        else:
813+            return qn(db_table)
814+
815+    def prep_db_index(self, db_schema, db_index):
816+        return self.prep_db_table(db_schema, db_index)
817+
818     def prep_for_iexact_query(self, x):
819         return x
820 
821@@ -273,27 +285,31 @@
822     def sql_flush(self, style, tables, sequences):
823         # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
824         # 'TRUNCATE z;'... style SQL statements
825+        sql = []
826         if tables:
827             # Oracle does support TRUNCATE, but it seems to get us into
828             # FK referential trouble, whereas DELETE FROM table works.
829-            sql = ['%s %s %s;' % \
830-                    (style.SQL_KEYWORD('DELETE'),
831-                     style.SQL_KEYWORD('FROM'),
832-                     style.SQL_FIELD(self.quote_name(table)))
833-                    for table in tables]
834+            for schema, table in tables:
835+                table = self.prep_db_table(schema, table)
836+                sql.append('%s %s %s;' % \
837+                           (style.SQL_KEYWORD('DELETE'),
838+                            style.SQL_KEYWORD('FROM'),
839+                            style.SQL_FIELD(table)))
840             # Since we've just deleted all the rows, running our sequence
841             # ALTER code will reset the sequence to 0.
842             for sequence_info in sequences:
843-                sequence_name = self._get_sequence_name(sequence_info['table'])
844-                table_name = self.quote_name(sequence_info['table'])
845+                schema_name = sequence_info['schema']
846+                sequence_name = self.prep_db_table(schema_name,
847+                                    get_sequence_name(sequence_info['table']))
848+                table_name = self.prep_db_table(schema_name,
849+                                                sequence_info['table'])
850+
851                 column_name = self.quote_name(sequence_info['column'] or 'id')
852                 query = _get_sequence_reset_sql() % {'sequence': sequence_name,
853                                                      'table': table_name,
854                                                      'column': column_name}
855                 sql.append(query)
856-            return sql
857-        else:
858-            return []
859+        return sql
860 
861     def sequence_reset_sql(self, style, model_list):
862         from django.db import models
863@@ -302,8 +318,10 @@
864         for model in model_list:
865             for f in model._meta.local_fields:
866                 if isinstance(f, models.AutoField):
867-                    table_name = self.quote_name(model._meta.db_table)
868-                    sequence_name = self._get_sequence_name(model._meta.db_table)
869+                    table_name = model._meta.qualified_name
870+                    sequence_name = self.prep_db_table(model._meta.db_schema,
871+                                       get_sequence_name(model._meta.db_table))
872+
873                     column_name = self.quote_name(f.column)
874                     output.append(query % {'sequence': sequence_name,
875                                            'table': table_name,
876@@ -313,8 +331,10 @@
877                     break
878             for f in model._meta.many_to_many:
879                 if not f.rel.through:
880-                    table_name = self.quote_name(f.m2m_db_table())
881-                    sequence_name = self._get_sequence_name(f.m2m_db_table())
882+                    table_name = self.quote_name(f.m2m_qualified_name())
883+                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
884+                                           get_sequence_name(f.m2m_db_table()))
885+
886                     column_name = self.quote_name('id')
887                     output.append(query % {'sequence': sequence_name,
888                                            'table': table_name,
889Index: django/db/backends/oracle/introspection.py
890===================================================================
891--- django/db/backends/oracle/introspection.py  (revision 16443)
892+++ django/db/backends/oracle/introspection.py  (working copy)
893@@ -120,3 +120,26 @@
894         for row in cursor.fetchall():
895             indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
896         return indexes
897+
898+    def schema_name_converter(self, name):
899+        """Convert to lowercase for case-sensitive schema name comparison."""
900+        return name.lower()
901+
902+    def get_schema_list(self, cursor):
903+        "Returns a list of schemas that exist in the database."
904+        sql = """
905+        select distinct username
906+        from all_users, all_objects
907+        where username = owner
908+        """
909+        cursor.execute(sql)
910+        return [schema.lower() for (schema,) in cursor]
911+
912+    def get_schema_table_list(self, cursor, schema):
913+        "Returns a list of tables in a specific schema."
914+        sql = """
915+        select table_name from all_tables
916+        where owner = upper(%s)
917+        """
918+        cursor.execute(sql, [schema])
919+        return [table.lower() for (table,) in cursor]
920Index: django/db/backends/oracle/creation.py
921===================================================================
922--- django/db/backends/oracle/creation.py       (revision 16443)
923+++ django/db/backends/oracle/creation.py       (working copy)
924@@ -38,12 +38,38 @@
925         'TimeField':                    'TIMESTAMP',
926         'URLField':                     'VARCHAR2(%(max_length)s)',
927     }
928-
929-    def __init__(self, connection):
930+       
931+       def __init__(self, connection):
932         self.remember = {}
933         super(DatabaseCreation, self).__init__(connection)
934+       
935+    def sql_create_schema(self, schema, style, password=None,
936+                          tablespace=None, temp_tablespace=None):
937+        qn = self.connection.ops.quote_name
938+        lock_account = (password is None)
939+        if lock_account:
940+            password = schema
941+        output = []
942+        output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'),
943+                                       qn(schema),
944+                                       style.SQL_KEYWORD('IDENTIFIED BY'),
945+                                       qn(password)))
946+        if tablespace:
947+            output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'),
948+                                     qn(tablespace)))
949+        if temp_tablespace:
950+            output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'),
951+                                     qn(temp_tablespace)))
952+        if lock_account:
953+            output.append(style.SQL_KEYWORD('ACCOUNT LOCK'))
954+        return '\n'.join(output)
955 
956-    def _create_test_db(self, verbosity=1, autoclobber=False):
957+    def sql_destroy_schema(self, schema, style):
958+        qn = self.connection.ops.quote_name
959+        return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema),
960+                             style.SQL_KEYWORD('CASCADE'))
961+
962+    def _create_test_db(self, verbosity=1, autoclobber=False, schema=None):
963         TEST_NAME = self._test_database_name()
964         TEST_USER = self._test_database_user()
965         TEST_PASSWD = self._test_database_passwd()
966@@ -169,7 +195,7 @@
967                DEFAULT TABLESPACE %(tblspace)s
968                TEMPORARY TABLESPACE %(tblspace_temp)s
969             """,
970-            """GRANT CONNECT, RESOURCE TO %(user)s""",
971+            """GRANT CONNECT, RESOURCE, CREATE USER, DROP USER, CREATE ANY TABLE, ALTER ANY TABLE, CREATE ANY INDEX, CREATE ANY SEQUENCE, CREATE ANY TRIGGER, SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE TO %(user)s""",
972         ]
973         self._execute_statements(cursor, statements, parameters, verbosity)
974 
975Index: django/db/backends/__init__.py
976===================================================================
977--- django/db/backends/__init__.py      (revision 16443)
978+++ django/db/backends/__init__.py      (working copy)
979@@ -279,6 +279,9 @@
980     # integer primary keys.
981     related_fields_match_type = False
982     allow_sliced_subqueries = True
983+       
984+    default_schema_name = ''
985+       
986     has_select_for_update = False
987     has_select_for_update_nowait = False
988 
989@@ -394,7 +397,7 @@
990         self.connection = connection
991         self._cache = None
992 
993-    def autoinc_sql(self, table, column):
994+    def autoinc_sql(self, schema, table, column):
995         """
996         Returns any SQL needed to support auto-incrementing primary keys, or
997         None if no SQL is necessary.
998@@ -446,7 +449,7 @@
999         """
1000         return "DROP CONSTRAINT"
1001 
1002-    def drop_sequence_sql(self, table):
1003+    def drop_sequence_sql(self, schema, table):
1004         """
1005         Returns any SQL necessary to drop the sequence for the given table.
1006         Returns None if no SQL is necessary.
1007@@ -516,7 +519,7 @@
1008 
1009         return smart_unicode(sql) % u_params
1010 
1011-    def last_insert_id(self, cursor, table_name, pk_name):
1012+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
1013         """
1014         Given a cursor object that has just performed an INSERT statement into
1015         a table that has an auto-incrementing ID, returns the newly created ID.
1016@@ -595,6 +598,20 @@
1017         """
1018         raise NotImplementedError()
1019 
1020+    def prep_db_table(self, db_schema, db_table):
1021+        """
1022+        Prepares and formats the table name if necessary.
1023+        Just returns quoted db_table if not supported.
1024+        """
1025+        return self.quote_name(db_table)
1026+
1027+    def prep_db_index(self, db_schema, db_index):
1028+        """
1029+        Prepares and formats the table index name if necessary.
1030+        Just returns quoted db_index if not supported.
1031+        """
1032+        return self.quote_name(db_index)
1033+
1034     def random_function_sql(self):
1035         """
1036         Returns a SQL expression that returns a random value.
1037@@ -799,35 +816,72 @@
1038         return name
1039 
1040     def table_names(self):
1041-        "Returns a list of names of all tables that exist in the database."
1042+        "Returns a list of names of all tables that exist in the default schema."
1043         cursor = self.connection.cursor()
1044         return self.get_table_list(cursor)
1045 
1046+    def schema_name_converter(self, name):
1047+        """Apply a conversion to the name for the purposes of comparison.
1048+
1049+        The default schema name converter is for case sensitive comparison.
1050+        """
1051+        return name
1052+
1053+    def get_schema_list(self, cursor):
1054+        "Returns a list of schemas that exist in the database"
1055+        return []
1056+
1057+    def get_schema_table_list(self, cursor, schema):
1058+        "Returns a list of tables in a specific schema"
1059+        return []
1060+
1061+    def schema_names(self):
1062+        cursor = self.connection.cursor()
1063+        return self.get_schema_list(cursor)
1064+
1065+    def schema_table_names(self, schema):
1066+        "Returns a list of names of all tables that exist in the database schema."
1067+        cursor = self.connection.cursor()
1068+        return self.get_schema_table_list(cursor, schema)
1069+
1070     def django_table_names(self, only_existing=False):
1071         """
1072-        Returns a list of all table names that have associated Django models and
1073-        are in INSTALLED_APPS.
1074+        Returns a list of tuples containing all schema and table names that
1075+        have associated Django models and are in INSTALLED_APPS.
1076 
1077-        If only_existing is True, the resulting list will only include the tables
1078-        that actually exist in the database.
1079+        If only_existing is True, the resulting list will only include the
1080+        tables that actually exist in the database.
1081         """
1082         from django.db import models, router
1083         tables = set()
1084-        for app in models.get_apps():
1085-            for model in models.get_models(app):
1086-                if not model._meta.managed:
1087-                    continue
1088-                if not router.allow_syncdb(self.connection.alias, model):
1089-                    continue
1090-                tables.add(model._meta.db_table)
1091-                tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
1092         if only_existing:
1093-            existing_tables = self.table_names()
1094-            tables = [
1095-                t
1096-                for t in tables
1097-                if self.table_name_converter(t) in existing_tables
1098-            ]
1099+            existing_tables = set([('', tn) for tn in self.table_names()])
1100+            seen_schemas = set()
1101+        for model in models.get_models():
1102+            if not model._meta.managed:
1103+                continue
1104+            if not router.allow_syncdb(self.connection.alias, model):
1105+                continue
1106+            db_schema = model._meta.db_schema
1107+            if only_existing and db_schema and db_schema not in seen_schemas:
1108+                existing_tables.update([(db_schema, tn) for tn in
1109+                                        self.schema_table_names(db_schema)])
1110+                seen_schemas.add(db_schema)
1111+            tables.add((db_schema, model._meta.db_table))
1112+            m2m_tables = []
1113+            for f in model._meta.local_many_to_many:
1114+                m2m_schema = f.m2m_db_schema()
1115+                m2m_table = f.m2m_db_table()
1116+                if only_existing and m2m_schema and m2m_schema not in seen_schemas:
1117+                    existing_tables.update([(m2m_schema, tn) for tn in
1118+                                        self.schema_table_names(m2m_schema)])
1119+                    seen_schemas.add(m2m_schema)
1120+                m2m_tables.append((m2m_schema, m2m_table))
1121+            tables.update(m2m_tables)
1122+        if only_existing:
1123+            tables = [(s, t) for (s, t) in tables
1124+                      if (self.schema_name_converter(s),
1125+                          self.table_name_converter(t)) in existing_tables]
1126         return tables
1127 
1128     def installed_models(self, tables):
1129@@ -859,14 +913,19 @@
1130                     continue
1131                 for f in model._meta.local_fields:
1132                     if isinstance(f, models.AutoField):
1133-                        sequence_list.append({'table': model._meta.db_table, 'column': f.column})
1134+                        sequence_list.append({'table': model._meta.db_table,
1135+                                              'column': f.column,
1136+                                              'schema': model._meta.db_schema})
1137                         break # Only one AutoField is allowed per model, so don't bother continuing.
1138 
1139                 for f in model._meta.local_many_to_many:
1140+                    schema = f.m2m_db_schema()
1141                     # If this is an m2m using an intermediate table,
1142                     # we don't need to reset the sequence.
1143                     if f.rel.through is None:
1144-                        sequence_list.append({'table': f.m2m_db_table(), 'column': None})
1145+                        sequence_list.append({'table': f.m2m_db_table(),
1146+                                              'column': None,
1147+                                              'schema': schema})
1148 
1149         return sequence_list
1150 
1151Index: django/db/backends/postgresql_psycopg2/base.py
1152===================================================================
1153--- django/db/backends/postgresql_psycopg2/base.py      (revision 16443)
1154+++ django/db/backends/postgresql_psycopg2/base.py      (working copy)
1155@@ -72,6 +72,7 @@
1156     can_defer_constraint_checks = True
1157     has_select_for_update = True
1158     has_select_for_update_nowait = True
1159+    default_schema_name = u'public'
1160 
1161 
1162 class DatabaseWrapper(BaseDatabaseWrapper):
1163Index: django/db/backends/creation.py
1164===================================================================
1165--- django/db/backends/creation.py      (revision 16443)
1166+++ django/db/backends/creation.py      (working copy)
1167@@ -26,6 +26,17 @@
1168         """
1169         return '%x' % (abs(hash(args)) % 4294967296L)  # 2**32
1170 
1171+    def default_schema(self):
1172+        return ""
1173+
1174+    def sql_create_schema(self, schema, style):
1175+        """"
1176+        Returns the SQL required to create a single schema
1177+        """
1178+        qn = self.connection.ops.quote_name
1179+        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
1180+        return output
1181+
1182     def sql_create_model(self, model, style, known_models=set()):
1183         """
1184         Returns the SQL required to create a single model, as a tuple of:
1185@@ -69,7 +80,7 @@
1186             table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
1187                 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
1188 
1189-        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
1190+        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' (']
1191         for i, line in enumerate(table_output): # Combine and add commas.
1192             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
1193         full_statement.append(')')
1194@@ -81,7 +92,9 @@
1195         if opts.has_auto_field:
1196             # Add any extra SQL needed to support auto-incrementing primary keys.
1197             auto_column = opts.auto_field.db_column or opts.auto_field.name
1198-            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
1199+            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema,
1200+                                                          opts.db_table,
1201+                                                          auto_column)
1202             if autoinc_sql:
1203                 for stmt in autoinc_sql:
1204                     final_output.append(stmt)
1205@@ -93,7 +106,7 @@
1206         qn = self.connection.ops.quote_name
1207         if field.rel.to in known_models:
1208             output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
1209-                style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
1210+                style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \
1211                 style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
1212                 self.connection.ops.deferrable_sql()
1213             ]
1214@@ -119,19 +132,132 @@
1215             for rel_class, f in pending_references[model]:
1216                 rel_opts = rel_class._meta
1217                 r_table = rel_opts.db_table
1218+                r_qname = rel_opts.qualified_name
1219                 r_col = f.column
1220                 table = opts.db_table
1221+                qname = opts.qualified_name
1222                 col = opts.get_field(f.rel.field_name).column
1223                 # For MySQL, r_name must be unique in the first 64 characters.
1224                 # So we are careful with character usage here.
1225                 r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
1226                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
1227-                    (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
1228-                    qn(r_col), qn(table), qn(col),
1229+                    (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())),
1230+                    qn(r_col), qname, qn(col),
1231                     self.connection.ops.deferrable_sql()))
1232             del pending_references[model]
1233         return final_output
1234 
1235+    def sql_for_many_to_many(self, model, style):
1236+        "Return the CREATE TABLE statments for all the many-to-many tables defined on a model"
1237+        import warnings
1238+        warnings.warn(
1239+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
1240+            PendingDeprecationWarning
1241+        )
1242+
1243+        output = []
1244+        for f in model._meta.local_many_to_many:
1245+            if model._meta.managed or f.rel.to._meta.managed:
1246+                output.extend(self.sql_for_many_to_many_field(model, f, style))
1247+        return output
1248+
1249+    def sql_for_many_to_many_field(self, model, f, style):
1250+        "Return the CREATE TABLE statements for a single m2m field"
1251+        import warnings
1252+        warnings.warn(
1253+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
1254+            PendingDeprecationWarning
1255+        )
1256+
1257+        from django.db import models
1258+        from django.db.backends.util import truncate_name
1259+
1260+        output = []
1261+        if f.rel.through._meta.auto_created:
1262+            opts = model._meta
1263+            qn = self.connection.ops.quote_name
1264+            tablespace = f.db_tablespace or opts.db_tablespace
1265+            if tablespace:
1266+                sql = self.connection.ops.tablespace_sql(tablespace, inline=True)
1267+                if sql:
1268+                    tablespace_sql = ' ' + sql
1269+                else:
1270+                    tablespace_sql = ''
1271+            else:
1272+                tablespace_sql = ''
1273+            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
1274+                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
1275+            table_output.append('    %s %s %s%s,' %
1276+                (style.SQL_FIELD(qn('id')),
1277+                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)),
1278+                style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
1279+                tablespace_sql))
1280+
1281+            deferred = []
1282+            inline_output, deferred = self.sql_for_inline_many_to_many_references(model, f, style)
1283+            table_output.extend(inline_output)
1284+
1285+            table_output.append('    %s (%s, %s)%s' %
1286+                (style.SQL_KEYWORD('UNIQUE'),
1287+                style.SQL_FIELD(qn(f.m2m_column_name())),
1288+                style.SQL_FIELD(qn(f.m2m_reverse_name())),
1289+                tablespace_sql))
1290+            table_output.append(')')
1291+            if opts.db_tablespace:
1292+                # f.db_tablespace is only for indices, so ignore its value here.
1293+                table_output.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
1294+            table_output.append(';')
1295+            output.append('\n'.join(table_output))
1296+
1297+            for r_table, r_col, table, col in deferred:
1298+                r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
1299+                output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
1300+                (qn(r_table),
1301+                qn(truncate_name(r_name, self.connection.ops.max_name_length())),
1302+                qn(r_col), qn(table), qn(col),
1303+                self.connection.ops.deferrable_sql()))
1304+
1305+            # Add any extra SQL needed to support auto-incrementing PKs
1306+            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
1307+                                                          f.m2m_db_table(),
1308+                                                          'id')
1309+            if autoinc_sql:
1310+                for stmt in autoinc_sql:
1311+                    output.append(stmt)
1312+        return output
1313+
1314+    def sql_for_inline_many_to_many_references(self, model, field, style):
1315+        "Create the references to other tables required by a many-to-many table"
1316+        import warnings
1317+        warnings.warn(
1318+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
1319+            PendingDeprecationWarning
1320+        )
1321+
1322+        from django.db import models
1323+        opts = model._meta
1324+        qn = self.connection.ops.quote_name
1325+
1326+        table_output = [
1327+            '    %s %s %s %s (%s)%s,' %
1328+                (style.SQL_FIELD(qn(field.m2m_column_name())),
1329+                style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)),
1330+                style.SQL_KEYWORD('NOT NULL REFERENCES'),
1331+                style.SQL_TABLE(opts.qualified_name),
1332+                style.SQL_FIELD(qn(opts.pk.column)),
1333+                self.connection.ops.deferrable_sql()),
1334+            '    %s %s %s %s (%s)%s,' %
1335+                (style.SQL_FIELD(qn(field.m2m_reverse_name())),
1336+                style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)),
1337+                style.SQL_KEYWORD('NOT NULL REFERENCES'),
1338+                style.SQL_TABLE(field.rel.to._meta.qualified_name),
1339+                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
1340+                self.connection.ops.deferrable_sql())
1341+        ]
1342+        deferred = []
1343+
1344+        return table_output, deferred
1345+
1346     def sql_indexes_for_model(self, model, style):
1347         "Returns the CREATE INDEX SQL statements for a single model"
1348         if not model._meta.managed or model._meta.proxy:
1349@@ -157,16 +283,25 @@
1350             else:
1351                 tablespace_sql = ''
1352             i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))
1353+            i_name = self.connection.ops.prep_db_index(model._meta.db_schema, i_name)
1354             output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
1355                 style.SQL_TABLE(qn(truncate_name(i_name, self.connection.ops.max_name_length()))) + ' ' +
1356                 style.SQL_KEYWORD('ON') + ' ' +
1357-                style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
1358+                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
1359                 "(%s)" % style.SQL_FIELD(qn(f.column)) +
1360                 "%s;" % tablespace_sql]
1361         else:
1362             output = []
1363         return output
1364 
1365+    def sql_destroy_schema(self, schema, style):
1366+        """"
1367+        Returns the SQL required to destroy a single schema.
1368+        """
1369+        qn = self.connection.ops.quote_name
1370+        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
1371+        return output
1372+
1373     def sql_destroy_model(self, model, references_to_delete, style):
1374         "Return the DROP TABLE and restraint dropping statements for a single model"
1375         if not model._meta.managed or model._meta.proxy:
1376@@ -174,12 +309,13 @@
1377         # Drop the table now
1378         qn = self.connection.ops.quote_name
1379         output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
1380-                              style.SQL_TABLE(qn(model._meta.db_table)))]
1381+                              style.SQL_TABLE(model._meta.qualified_name))]
1382         if model in references_to_delete:
1383             output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
1384 
1385         if model._meta.has_auto_field:
1386-            ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
1387+            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
1388+                                                       model._meta.db_table)
1389             if ds:
1390                 output.append(ds)
1391         return output
1392@@ -193,18 +329,38 @@
1393         qn = self.connection.ops.quote_name
1394         for rel_class, f in references_to_delete[model]:
1395             table = rel_class._meta.db_table
1396+            qname = rel_class._meta.qualified_name
1397             col = f.column
1398             r_table = model._meta.db_table
1399             r_col = model._meta.get_field(f.rel.field_name).column
1400             r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
1401             output.append('%s %s %s %s;' % \
1402                 (style.SQL_KEYWORD('ALTER TABLE'),
1403-                style.SQL_TABLE(qn(table)),
1404+                style.SQL_TABLE(qname),
1405                 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
1406                 style.SQL_FIELD(qn(truncate_name(r_name, self.connection.ops.max_name_length())))))
1407         del references_to_delete[model]
1408         return output
1409 
1410+    def sql_destroy_many_to_many(self, model, f, style):
1411+        "Returns the DROP TABLE statements for a single m2m field"
1412+        import warnings
1413+        warnings.warn(
1414+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
1415+            PendingDeprecationWarning
1416+        )
1417+
1418+        qn = self.connection.ops.quote_name
1419+        output = []
1420+        if f.rel.through._meta.auto_created:
1421+            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
1422+                style.SQL_TABLE(f.m2m_qualified_name())))
1423+            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
1424+                               "%s_%s" % (model._meta.db_table, f.column))
1425+            if ds:
1426+                output.append(ds)
1427+        return output
1428+
1429     def create_test_db(self, verbosity=1, autoclobber=False):
1430         """
1431         Creates a test database, prompting the user for confirmation if the
1432@@ -221,11 +377,18 @@
1433                 test_db_repr = " ('%s')" % test_database_name
1434             print "Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr)
1435 
1436-        self._create_test_db(verbosity, autoclobber)
1437+        schema_apps = self._get_app_with_schemas()
1438+        schemas = self._get_schemas(schema_apps)
1439+        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
1440 
1441         self.connection.close()
1442         self.connection.settings_dict["NAME"] = test_database_name
1443 
1444+        # Create the test schemas.
1445+        cursor = self.connection.cursor()
1446+        self._create_test_schemas(verbosity, schemas, cursor)
1447+
1448+        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
1449         # Confirm the feature set of the test database
1450         self.connection.features.confirm()
1451 
1452@@ -271,6 +434,49 @@
1453 
1454         return test_database_name
1455 
1456+    def _create_test_schemas(self, verbosity, schemas, cursor):
1457+        from django.core.management.color import no_style
1458+        style = no_style()
1459+        for schema in schemas:
1460+            if verbosity >= 1:
1461+                print "Creating schema %s" % schema
1462+            cursor.execute(self.sql_create_schema(schema, style))
1463+
1464+    def _destroy_test_schemas(self, verbosity, schemas, cursor):
1465+        from django.core.management.color import no_style
1466+        style = no_style()
1467+        for schema in schemas:
1468+            if verbosity >= 1:
1469+                print "Destroying schema %s" % schema
1470+            cursor.execute(self.sql_destroy_schema(schema, style))
1471+            if verbosity >= 1:
1472+                print "Schema %s destroyed" % schema
1473+
1474+    def _get_schemas(self, apps):
1475+        from django.db import models
1476+        schemas = set()
1477+        for app in apps:
1478+            app_models = models.get_models(app)
1479+            for model in app_models:
1480+                schema = model._meta.db_schema
1481+                if not schema or schema in schemas:
1482+                    continue
1483+                schemas.add(schema)
1484+        return schemas
1485+
1486+    def _get_app_with_schemas(self):
1487+        from django.db import models
1488+        apps = models.get_apps()
1489+        schema_apps = set()
1490+        for app in apps:
1491+            app_models = models.get_models(app)
1492+            for model in app_models:
1493+                schema = model._meta.db_schema
1494+                if not schema or app in schema_apps:
1495+                    continue
1496+                schema_apps.add(app)
1497+        return schema_apps
1498+
1499     def _get_test_db_name(self):
1500         """
1501         Internal implementation - returns the name of the test DB that will be
1502@@ -282,7 +488,7 @@
1503             return self.connection.settings_dict['TEST_NAME']
1504         return TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
1505 
1506-    def _create_test_db(self, verbosity, autoclobber):
1507+    def _create_test_db(self, verbosity, autoclobber, schemas):
1508         "Internal implementation - creates the test db tables."
1509         suffix = self.sql_table_creation_suffix()
1510 
1511@@ -305,6 +511,7 @@
1512                 try:
1513                     if verbosity >= 1:
1514                         print "Destroying old test database '%s'..." % self.connection.alias
1515+                    self._destroy_test_schemas(verbosity, schemas, cursor)
1516                     cursor.execute("DROP DATABASE %s" % qn(test_database_name))
1517                     cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
1518                 except Exception, e:
1519Index: django/conf/project_template/settings.py
1520===================================================================
1521--- django/conf/project_template/settings.py    (revision 16443)
1522+++ django/conf/project_template/settings.py    (working copy)
1523@@ -17,6 +17,7 @@
1524         'PASSWORD': '',                  # Not used with sqlite3.
1525         'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
1526         'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
1527+        'SCHEMA': '',                    # Set to empty string for default.
1528     }
1529 }
1530 
1531Index: django/conf/global_settings.py
1532===================================================================
1533--- django/conf/global_settings.py      (revision 16443)
1534+++ django/conf/global_settings.py      (working copy)
1535@@ -150,6 +150,7 @@
1536 DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
1537 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
1538 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
1539+DATABASE_SCHEMA = ''           # Set to empty string for default.
1540 
1541 # New format
1542 DATABASES = {
1543Index: django/core/management/commands/syncdb.py
1544===================================================================
1545--- django/core/management/commands/syncdb.py   (revision 16443)
1546+++ django/core/management/commands/syncdb.py   (working copy)
1547@@ -56,8 +56,21 @@
1548         cursor = connection.cursor()
1549 
1550         # Get a list of already installed *models* so that references work right.
1551-        tables = connection.introspection.table_names()
1552+        schemas = connection.introspection.schema_names()
1553+        if schemas:
1554+            tables = []
1555+            default_schema_name = connection.features.default_schema_name
1556+            for schema in connection.introspection.schema_names():
1557+               if default_schema_name and schema == default_schema_name:
1558+                   sn = ''
1559+               else:
1560+                   sn = schema
1561+               for tn in connection.introspection.schema_table_names(schema):
1562+                   tables.append((sn, tn))
1563+        else:
1564+            tables = [('', tn) for tn in connection.introspection.table_names()]
1565         seen_models = connection.introspection.installed_models(tables)
1566+        seen_schemas = set()
1567         created_models = set()
1568         pending_references = {}
1569 
1570@@ -68,11 +81,23 @@
1571                 if router.allow_syncdb(db, m)])
1572             for app in models.get_apps()
1573         ]
1574+
1575+        def model_schema(model):
1576+            db_schema = model._meta.db_schema
1577+            if db_schema:
1578+                db_schema = connection.introspection.table_name_converter(db_schema)
1579+            return db_schema
1580+
1581         def model_installed(model):
1582             opts = model._meta
1583             converter = connection.introspection.table_name_converter
1584-            return not ((converter(opts.db_table) in tables) or
1585-                (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
1586+            db_schema = model_schema(model)
1587+            schema_table = (db_schema, converter(opts.db_table))
1588+            return not ((schema_table in tables) or
1589+                (opts.auto_created and \
1590+                 (db_schema, converter(opts.auto_created._meta.db_table)) in tables)
1591+                 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables)
1592+             )
1593 
1594         manifest = SortedDict(
1595             (app_name, filter(model_installed, model_list))
1596@@ -84,6 +109,13 @@
1597             print "Creating tables ..."
1598         for app_name, model_list in manifest.items():
1599             for model in model_list:
1600+                # Add model-defined schema tables if any.
1601+                db_schema = model_schema(model)
1602+                if db_schema and db_schema not in seen_schemas:
1603+                    tables += [(db_schema, tn) for tn in
1604+                               connection.introspection.schema_table_names(db_schema)]
1605+                    seen_schemas.add(db_schema)
1606+
1607                 # Create the model's database table, if it doesn't already exist.
1608                 if verbosity >= 3:
1609                     print "Processing %s.%s model" % (app_name, model._meta.object_name)
1610@@ -96,10 +128,14 @@
1611                         sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
1612                 sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
1613                 if verbosity >= 1 and sql:
1614-                    print "Creating table %s" % model._meta.db_table
1615+                    if db_schema:
1616+                        print "Creating table %s.%s" % (db_schema, model._meta.db_table)
1617+                    else:
1618+                        print "Creating table %s" % model._meta.db_table
1619                 for statement in sql:
1620                     cursor.execute(statement)
1621-                tables.append(connection.introspection.table_name_converter(model._meta.db_table))
1622+                if sql:
1623+                    tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table)))
1624 
1625 
1626         transaction.commit_unless_managed(using=db)
1627Index: django/core/management/sql.py
1628===================================================================
1629--- django/core/management/sql.py       (revision 16443)
1630+++ django/core/management/sql.py       (working copy)
1631@@ -63,7 +63,8 @@
1632 
1633     # Figure out which tables already exist
1634     if cursor:
1635-        table_names = connection.introspection.get_table_list(cursor)
1636+        table_names = [('', tn) for tn in
1637+                       connection.introspection.get_table_list(cursor)]
1638     else:
1639         table_names = []
1640 
1641@@ -74,8 +75,19 @@
1642 
1643     references_to_delete = {}
1644     app_models = models.get_models(app, include_auto_created=True)
1645+    seen_schemas = set()
1646     for model in app_models:
1647-        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
1648+        db_schema = model._meta.db_schema
1649+        # Find additional tables in model-defined schemas.
1650+        if db_schema:
1651+            db_schema = connection.introspection.schema_name_converter(db_schema)
1652+            if db_schema not in seen_schemas:
1653+                table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)]
1654+                seen_schemas.add(db_schema)
1655+        schema_table = (db_schema,
1656+                        connection.introspection.table_name_converter(model._meta.db_table))
1657+
1658+        if cursor and schema_table in table_names:
1659             # The table exists, so it needs to be dropped
1660             opts = model._meta
1661             for f in opts.local_fields:
1662@@ -85,7 +97,12 @@
1663             to_delete.add(model)
1664 
1665     for model in app_models:
1666-        if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
1667+        db_schema = model._meta.db_schema
1668+        if db_schema:
1669+            db_schema = connection.introspection.schema_name_converter(db_schema)
1670+        schema_table = (db_schema,
1671+                        connection.introspection.table_name_converter(model._meta.db_table))
1672+        if schema_table in table_names:
1673             output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
1674 
1675     # Close database connection explicitly, in case this output is being piped
1676@@ -116,7 +133,7 @@
1677     if only_django:
1678         tables = connection.introspection.django_table_names(only_existing=True)
1679     else:
1680-        tables = connection.introspection.table_names()
1681+        tables = [('', tn) for tn in connection.introspection.table_names()]
1682     statements = connection.ops.sql_flush(
1683         style, tables, connection.introspection.sequence_list()
1684     )
1685Index: django/contrib/contenttypes/generic.py
1686===================================================================
1687--- django/contrib/contenttypes/generic.py      (revision 16443)
1688+++ django/contrib/contenttypes/generic.py      (working copy)
1689@@ -133,6 +133,14 @@
1690     def m2m_reverse_name(self):
1691         return self.rel.to._meta.pk.column
1692 
1693+    def m2m_db_schema(self):
1694+        return self.rel.to._meta.db_schema
1695+
1696+    def m2m_qualified_name(self):
1697+        schema = self.m2m_db_schema()
1698+        table = self.m2m_db_table()
1699+        return connection.ops.prep_db_table(schema, table)
1700+
1701     def m2m_target_field_name(self):
1702         return self.model._meta.pk.name
1703 
1704Index: tests/regressiontests/introspection/tests.py
1705===================================================================
1706--- tests/regressiontests/introspection/tests.py        (revision 16443)
1707+++ tests/regressiontests/introspection/tests.py        (working copy)
1708@@ -58,7 +58,7 @@
1709 
1710     def test_sequence_list(self):
1711         sequences = connection.introspection.sequence_list()
1712-        expected = {'table': Reporter._meta.db_table, 'column': 'id'}
1713+        expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''}
1714         self.assertTrue(expected in sequences,
1715                      'Reporter sequence not found in sequence_list()')
1716 
1717Index: tests/regressiontests/backends/tests.py
1718===================================================================
1719--- tests/regressiontests/backends/tests.py     (revision 16443)
1720+++ tests/regressiontests/backends/tests.py     (working copy)
1721@@ -157,22 +157,23 @@
1722         # A full flush is expensive to the full test, so we dig into the
1723         # internals to generate the likely offending SQL and run it manually
1724 
1725-        # Some convenience aliases
1726-        VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
1727-        VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
1728-        tables = [
1729-            VLM._meta.db_table,
1730-            VLM_m2m._meta.db_table,
1731-        ]
1732-        sequences = [
1733-            {
1734-                'column': VLM._meta.pk.column,
1735-                'table': VLM._meta.db_table
1736-            },
1737-        ]
1738-        cursor = connection.cursor()
1739-        for statement in connection.ops.sql_flush(no_style(), tables, sequences):
1740-            cursor.execute(statement)
1741+               # Some convenience aliases
1742+               VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
1743+               VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
1744+               tables = [
1745+                       (VLM._meta.db_schema, VLM._meta.db_table),
1746+                       (VLM_m2m._meta.db_schema, VLM_m2m._meta.db_table),
1747+               ]
1748+               sequences = [
1749+                       {
1750+                               'column': VLM._meta.pk.column,
1751+                               'table': VLM._meta.db_table,
1752+                               'schema': VLM._meta.db_schema,
1753+                       },
1754+               ]
1755+               cursor = connection.cursor()
1756+               for statement in connection.ops.sql_flush(no_style(), tables, sequences):
1757+                       cursor.execute(statement)
1758 
1759 class SequenceResetTest(TestCase):
1760     def test_generic_relation(self):