Code

Ticket #6148: generic-db_schema-r11231.diff

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