Code

Ticket #6148: generic-db_schema-r11871.diff

File generic-db_schema-r11871.diff, 71.4 KB (added by kmpm, 4 years ago)

fixes a m2m issue

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