Ticket #6148: 6148-r13366.diff

File 6148-r13366.diff, 77.6 KB (added by Ramiro Morales, 14 years ago)

Patch updated to r13366

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    a b  
    145145DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
    146146DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
    147147DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
     148DATABASE_SCHEMA = ''           # Set to empty string for default.
    148149
    149150# New format
    150151DATABASES = {
  • django/conf/project_template/settings.py

    diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
    a b  
    1717        'PASSWORD': '',                  # Not used with sqlite3.
    1818        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
    1919        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
     20        'SCHEMA': '',                    # Set to empty string for default.
    2021    }
    2122}
    2223
  • django/contrib/contenttypes/generic.py

    diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
    a b  
    131131    def m2m_reverse_name(self):
    132132        return self.rel.to._meta.pk.column
    133133
     134    def m2m_db_schema(self):
     135        return self.rel.to._meta.db_schema
     136
     137    def m2m_qualified_name(self):
     138        schema = self.m2m_db_schema()
     139        table = self.m2m_db_table()
     140        return connection.ops.prep_db_table(schema, table)
     141
    134142    def contribute_to_class(self, cls, name):
    135143        super(GenericRelation, self).contribute_to_class(cls, name)
    136144
  • django/core/management/commands/syncdb.py

    diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
    a b  
    5252        cursor = connection.cursor()
    5353
    5454        # Get a list of already installed *models* so that references work right.
    55         tables = connection.introspection.table_names()
     55        schemas = connection.introspection.schema_names()
     56        if schemas:
     57            tables = []
     58            default_schema_name = connection.features.default_schema_name
     59            for schema in connection.introspection.schema_names():
     60               if default_schema_name and schema == default_schema_name:
     61                   sn = ''
     62               else:
     63                   sn = schema
     64               for tn in connection.introspection.schema_table_names(schema):
     65                   tables.append((sn, tn))
     66        else:
     67            tables = [('', tn) for tn in connection.introspection.table_names()]
    5668        seen_models = connection.introspection.installed_models(tables)
     69        seen_schemas = set()
    5770        created_models = set()
    5871        pending_references = {}
    5972
     
    6477                if router.allow_syncdb(db, m)])
    6578            for app in models.get_apps()
    6679        ]
     80
     81        def model_schema(model):
     82            db_schema = model._meta.db_schema
     83            if db_schema:
     84                db_schema = connection.introspection.table_name_converter(db_schema)
     85            return db_schema
     86
    6787        def model_installed(model):
    6888            opts = model._meta
    6989            converter = connection.introspection.table_name_converter
    70             return not ((converter(opts.db_table) in tables) or
    71                 (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
     90            db_schema = model_schema(model)
     91            schema_table = (db_schema, converter(opts.db_table))
     92            return not ((schema_table in tables) or
     93                (opts.auto_created and \
     94                 (db_schema, converter(opts.auto_created._meta.db_table)) in tables)
     95                 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables)
     96             )
    7297
    7398        manifest = SortedDict(
    7499            (app_name, filter(model_installed, model_list))
     
    78103        # Create the tables for each model
    79104        for app_name, model_list in manifest.items():
    80105            for model in model_list:
     106                # Add model-defined schema tables if any.
     107                db_schema = model_schema(model)
     108                if db_schema and db_schema not in seen_schemas:
     109                    tables += [(db_schema, tn) for tn in
     110                               connection.introspection.schema_table_names(db_schema)]
     111                    seen_schemas.add(db_schema)
     112
    81113                # Create the model's database table, if it doesn't already exist.
    82114                if verbosity >= 2:
    83115                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
     
    90122                        sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
    91123                sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
    92124                if verbosity >= 1 and sql:
    93                     print "Creating table %s" % model._meta.db_table
     125                    if db_schema:
     126                        print "Creating table %s.%s" % (db_schema, model._meta.db_table)
     127                    else:
     128                        print "Creating table %s" % model._meta.db_table
    94129                for statement in sql:
    95130                    cursor.execute(statement)
    96                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
     131                if sql:
     132                    tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table)))
    97133
    98134
    99135        transaction.commit_unless_managed(using=db)
  • django/core/management/sql.py

    diff --git a/django/core/management/sql.py b/django/core/management/sql.py
    a b  
    6666
    6767    # Figure out which tables already exist
    6868    if cursor:
    69         table_names = connection.introspection.get_table_list(cursor)
     69        table_names = [('', tn) for tn in
     70                       connection.introspection.get_table_list(cursor)]
    7071    else:
    7172        table_names = []
    7273
     
    7778
    7879    references_to_delete = {}
    7980    app_models = models.get_models(app, include_auto_created=True)
     81    seen_schemas = set()
    8082    for model in app_models:
    81         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     83        db_schema = model._meta.db_schema
     84        # Find additional tables in model-defined schemas.
     85        if db_schema:
     86            db_schema = connection.introspection.schema_name_converter(db_schema)
     87            if db_schema not in seen_schemas:
     88                table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)]
     89                seen_schemas.add(db_schema)
     90        schema_table = (db_schema,
     91                        connection.introspection.table_name_converter(model._meta.db_table))
     92
     93        if cursor and schema_table in table_names:
    8294            # The table exists, so it needs to be dropped
    8395            opts = model._meta
    8496            for f in opts.local_fields:
     
    88100            to_delete.add(model)
    89101
    90102    for model in app_models:
    91         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     103        db_schema = model._meta.db_schema
     104        if db_schema:
     105            db_schema = connection.introspection.schema_name_converter(db_schema)
     106        schema_table = (db_schema,
     107                        connection.introspection.table_name_converter(model._meta.db_table))
     108        if schema_table in table_names:
    92109            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
    93110
    94111    # Close database connection explicitly, in case this output is being piped
     
    113130    if only_django:
    114131        tables = connection.introspection.django_table_names(only_existing=True)
    115132    else:
    116         tables = connection.introspection.table_names()
     133        tables = [('', tn) for tn in connection.introspection.table_names()]
    117134    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
    118135    return statements
    119136
  • django/db/__init__.py

    diff --git a/django/db/__init__.py b/django/db/__init__.py
    a b  
    2929        'TEST_CHARSET': settings.TEST_DATABASE_CHARSET,
    3030        'TEST_COLLATION': settings.TEST_DATABASE_COLLATION,
    3131        'TEST_NAME': settings.TEST_DATABASE_NAME,
     32        'SCHEMA': settings.DATABASE_SCHEMA,
    3233    }
    3334
    3435if DEFAULT_DB_ALIAS not in settings.DATABASES:
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    a b  
    9696    # integer primary keys.
    9797    related_fields_match_type = False
    9898    allow_sliced_subqueries = True
     99    default_schema_name = ''
    99100
    100101class BaseDatabaseOperations(object):
    101102    """
     
    108109    def __init__(self):
    109110        self._cache = {}
    110111
    111     def autoinc_sql(self, table, column):
     112    def autoinc_sql(self, schema, table, column):
    112113        """
    113114        Returns any SQL needed to support auto-incrementing primary keys, or
    114115        None if no SQL is necessary.
     
    154155        """
    155156        return "DROP CONSTRAINT"
    156157
    157     def drop_sequence_sql(self, table):
     158    def drop_sequence_sql(self, schema, table):
    158159        """
    159160        Returns any SQL necessary to drop the sequence for the given table.
    160161        Returns None if no SQL is necessary.
     
    215216
    216217        return smart_unicode(sql) % u_params
    217218
    218     def last_insert_id(self, cursor, table_name, pk_name):
     219    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
    219220        """
    220221        Given a cursor object that has just performed an INSERT statement into
    221222        a table that has an auto-incrementing ID, returns the newly created ID.
     
    289290        """
    290291        raise NotImplementedError()
    291292
     293    def prep_db_table(self, db_schema, db_table):
     294        """
     295        Prepares and formats the table name if necessary.
     296        Just returns quoted db_table if not supported.
     297        """
     298        return self.quote_name(db_table)
     299
     300    def prep_db_index(self, db_schema, db_index):
     301        """
     302        Prepares and formats the table index name if necessary.
     303        Just returns quoted db_index if not supported.
     304        """
     305        return self.quote_name(db_index)
     306
    292307    def random_function_sql(self):
    293308        """
    294309        Returns a SQL expression that returns a random value.
     
    493508        return name
    494509
    495510    def table_names(self):
    496         "Returns a list of names of all tables that exist in the database."
     511        "Returns a list of names of all tables that exist in the default schema."
    497512        cursor = self.connection.cursor()
    498513        return self.get_table_list(cursor)
    499514
     515    def schema_name_converter(self, name):
     516        """Apply a conversion to the name for the purposes of comparison.
     517
     518        The default schema name converter is for case sensitive comparison.
     519        """
     520        return name
     521
     522    def get_schema_list(self, cursor):
     523        "Returns a list of schemas that exist in the database"
     524        return []
     525
     526    def get_schema_table_list(self, cursor, schema):
     527        "Returns a list of tables in a specific schema"
     528        return []
     529
     530    def schema_names(self):
     531        cursor = self.connection.cursor()
     532        return self.get_schema_list(cursor)
     533
     534    def schema_table_names(self, schema):
     535        "Returns a list of names of all tables that exist in the database schema."
     536        cursor = self.connection.cursor()
     537        return self.get_schema_table_list(cursor, schema)
     538
    500539    def django_table_names(self, only_existing=False):
    501540        """
    502         Returns a list of all table names that have associated Django models and
    503         are in INSTALLED_APPS.
     541        Returns a list of tuples containing all schema and table names that
     542        have associated Django models and are in INSTALLED_APPS.
    504543
    505         If only_existing is True, the resulting list will only include the tables
    506         that actually exist in the database.
     544        If only_existing is True, the resulting list will only include the
     545        tables that actually exist in the database.
    507546        """
    508547        from django.db import models, router
    509548        tables = set()
    510         for app in models.get_apps():
    511             for model in models.get_models(app):
    512                 if not model._meta.managed:
    513                     continue
    514                 if not router.allow_syncdb(self.connection.alias, model):
    515                     continue
    516                 tables.add(model._meta.db_table)
    517                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    518549        if only_existing:
    519             tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
     550            existing_tables = set([('', tn) for tn in self.table_names()])
     551            seen_schemas = set()
     552        for model in models.get_models():
     553            if not model._meta.managed:
     554                continue
     555            if not router.allow_syncdb(self.connection.alias, model):
     556                continue
     557            db_schema = model._meta.db_schema
     558            if only_existing and db_schema and db_schema not in seen_schemas:
     559                existing_tables.update([(db_schema, tn) for tn in
     560                                        self.schema_table_names(db_schema)])
     561                seen_schemas.add(db_schema)
     562            tables.add((db_schema, model._meta.db_table))
     563            m2m_tables = []
     564            for f in model._meta.local_many_to_many:
     565                m2m_schema = f.m2m_db_schema()
     566                m2m_table = f.m2m_db_table()
     567                if only_existing and m2m_schema and m2m_schema not in seen_schemas:
     568                    existing_tables.update([(m2m_schema, tn) for tn in
     569                                        self.schema_table_names(m2m_schema)])
     570                    seen_schemas.add(m2m_schema)
     571                m2m_tables.append((m2m_schema, m2m_table))
     572            tables.update(m2m_tables)
     573        if only_existing:
     574            tables = [(s, t) for (s, t) in tables
     575                      if (self.schema_name_converter(s),
     576                          self.table_name_converter(t)) in existing_tables]
    520577        return tables
    521578
    522579    def installed_models(self, tables):
     
    546603                    continue
    547604                for f in model._meta.local_fields:
    548605                    if isinstance(f, models.AutoField):
    549                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
     606                        sequence_list.append({'table': model._meta.db_table,
     607                                              'column': f.column,
     608                                              'schema': model._meta.db_schema})
    550609                        break # Only one AutoField is allowed per model, so don't bother continuing.
    551610
    552611                for f in model._meta.local_many_to_many:
     612                    schema = f.m2m_db_schema()
    553613                    # If this is an m2m using an intermediate table,
    554614                    # we don't need to reset the sequence.
    555615                    if f.rel.through is None:
    556                         sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     616                        sequence_list.append({'table': f.m2m_db_table(),
     617                                              'column': None,
     618                                              'schema': schema})
    557619
    558620        return sequence_list
    559621
  • django/db/backends/creation.py

    diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
    a b  
    2727        """
    2828        return '%x' % (abs(hash(args)) % 4294967296L)  # 2**32
    2929
     30    def default_schema(self):
     31        return ""
     32
     33    def sql_create_schema(self, schema, style):
     34        """"
     35        Returns the SQL required to create a single schema
     36        """
     37        qn = self.connection.ops.quote_name
     38        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
     39        return output
     40
    3041    def sql_create_model(self, model, style, known_models=set()):
    3142        """
    3243        Returns the SQL required to create a single model, as a tuple of:
     
    7283            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    7384                ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
    7485
    75         full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     86        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' (']
    7687        for i, line in enumerate(table_output): # Combine and add commas.
    7788            full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    7889        full_statement.append(')')
     
    8495        if opts.has_auto_field:
    8596            # Add any extra SQL needed to support auto-incrementing primary keys.
    8697            auto_column = opts.auto_field.db_column or opts.auto_field.name
    87             autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
     98            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema,
     99                                                          opts.db_table,
     100                                                          auto_column)
    88101            if autoinc_sql:
    89102                for stmt in autoinc_sql:
    90103                    final_output.append(stmt)
     
    96109        qn = self.connection.ops.quote_name
    97110        if field.rel.to in known_models:
    98111            output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
    99                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
     112                style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \
    100113                style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
    101114                self.connection.ops.deferrable_sql()
    102115            ]
     
    122135            for rel_class, f in pending_references[model]:
    123136                rel_opts = rel_class._meta
    124137                r_table = rel_opts.db_table
     138                r_qname = rel_opts.qualified_name
    125139                r_col = f.column
    126140                table = opts.db_table
     141                qname = opts.qualified_name
    127142                col = opts.get_field(f.rel.field_name).column
    128143                # For MySQL, r_name must be unique in the first 64 characters.
    129144                # So we are careful with character usage here.
    130145                r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
    131146                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    132                     (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
    133                     qn(r_col), qn(table), qn(col),
     147                    (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())),
     148                    qn(r_col), qname, qn(col),
    134149                    self.connection.ops.deferrable_sql()))
    135150            del pending_references[model]
    136151        return final_output
     
    161176        from django.db.backends.util import truncate_name
    162177
    163178        output = []
    164         if f.auto_created:
     179        if f.rel.through._meta.auto_created:
    165180            opts = model._meta
    166181            qn = self.connection.ops.quote_name
    167182            tablespace = f.db_tablespace or opts.db_tablespace
     
    174189            else:
    175190                tablespace_sql = ''
    176191            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
    177                 style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     192                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
    178193            table_output.append('    %s %s %s%s,' %
    179194                (style.SQL_FIELD(qn('id')),
    180195                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)),
     
    206221                self.connection.ops.deferrable_sql()))
    207222
    208223            # Add any extra SQL needed to support auto-incrementing PKs
    209             autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
     224            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
     225                                                          f.m2m_db_table(),
     226                                                          'id')
    210227            if autoinc_sql:
    211228                for stmt in autoinc_sql:
    212229                    output.append(stmt)
     
    229246                (style.SQL_FIELD(qn(field.m2m_column_name())),
    230247                style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)),
    231248                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    232                 style.SQL_TABLE(qn(opts.db_table)),
     249                style.SQL_TABLE(opts.qualified_name),
    233250                style.SQL_FIELD(qn(opts.pk.column)),
    234251                self.connection.ops.deferrable_sql()),
    235252            '    %s %s %s %s (%s)%s,' %
    236253                (style.SQL_FIELD(qn(field.m2m_reverse_name())),
    237254                style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)),
    238255                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    239                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
     256                style.SQL_TABLE(field.rel.to._meta.qualified_name),
    240257                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
    241258                self.connection.ops.deferrable_sql())
    242259        ]
     
    269286            else:
    270287                tablespace_sql = ''
    271288            i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))
     289            i_name = self.connection.ops.prep_db_index(model._meta.db_schema, i_name)
    272290            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    273291                style.SQL_TABLE(qn(truncate_name(i_name, self.connection.ops.max_name_length()))) + ' ' +
    274292                style.SQL_KEYWORD('ON') + ' ' +
    275                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
     293                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    276294                "(%s)" % style.SQL_FIELD(qn(f.column)) +
    277295                "%s;" % tablespace_sql]
    278296        else:
    279297            output = []
    280298        return output
    281299
     300    def sql_destroy_schema(self, schema, style):
     301        """"
     302        Returns the SQL required to destroy a single schema.
     303        """
     304        qn = self.connection.ops.quote_name
     305        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
     306        return output
     307
    282308    def sql_destroy_model(self, model, references_to_delete, style):
    283309        "Return the DROP TABLE and restraint dropping statements for a single model"
    284310        if not model._meta.managed or model._meta.proxy:
     
    286312        # Drop the table now
    287313        qn = self.connection.ops.quote_name
    288314        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
    289                               style.SQL_TABLE(qn(model._meta.db_table)))]
     315                              style.SQL_TABLE(model._meta.qualified_name))]
    290316        if model in references_to_delete:
    291317            output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
    292318
    293319        if model._meta.has_auto_field:
    294             ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
     320            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     321                                                       model._meta.db_table)
    295322            if ds:
    296323                output.append(ds)
    297324        return output
     
    305332        qn = self.connection.ops.quote_name
    306333        for rel_class, f in references_to_delete[model]:
    307334            table = rel_class._meta.db_table
     335            qname = rel_class._meta.qualified_name
    308336            col = f.column
    309337            r_table = model._meta.db_table
    310338            r_col = model._meta.get_field(f.rel.field_name).column
    311339            r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
    312340            output.append('%s %s %s %s;' % \
    313341                (style.SQL_KEYWORD('ALTER TABLE'),
    314                 style.SQL_TABLE(qn(table)),
     342                style.SQL_TABLE(qname),
    315343                style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
    316344                style.SQL_FIELD(qn(truncate_name(r_name, self.connection.ops.max_name_length())))))
    317345        del references_to_delete[model]
     
    327355
    328356        qn = self.connection.ops.quote_name
    329357        output = []
    330         if f.auto_created:
     358        if f.rel.through._meta.auto_created:
    331359            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
    332                 style.SQL_TABLE(qn(f.m2m_db_table()))))
    333             ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
     360                style.SQL_TABLE(f.m2m_qualified_name())))
     361            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     362                               "%s_%s" % (model._meta.db_table, f.column))
    334363            if ds:
    335364                output.append(ds)
    336365        return output
     
    343372        if verbosity >= 1:
    344373            print "Creating test database '%s'..." % self.connection.alias
    345374
    346         test_database_name = self._create_test_db(verbosity, autoclobber)
     375        schema_apps = self._get_app_with_schemas()
     376        schemas = self._get_schemas(schema_apps)
     377        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
    347378
    348379        self.connection.close()
    349380        self.connection.settings_dict["NAME"] = test_database_name
    350381        can_rollback = self._rollback_works()
    351382        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
    352383
     384        # Create the test schemas.
     385        cursor = self.connection.cursor()
     386        self._create_test_schemas(verbosity, schemas, cursor)
     387
    353388        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
    354389
    355390        if settings.CACHE_BACKEND.startswith('db://'):
     
    363398
    364399        return test_database_name
    365400
    366     def _create_test_db(self, verbosity, autoclobber):
     401    def _create_test_schemas(self, verbosity, schemas, cursor):
     402        from django.core.management.color import no_style
     403        style = no_style()
     404        for schema in schemas:
     405            if verbosity >= 1:
     406                print "Creating schema %s" % schema
     407            cursor.execute(self.sql_create_schema(schema, style))
     408
     409    def _destroy_test_schemas(self, verbosity, schemas, cursor):
     410        from django.core.management.color import no_style
     411        style = no_style()
     412        for schema in schemas:
     413            if verbosity >= 1:
     414                print "Destroying schema %s" % schema
     415            cursor.execute(self.sql_destroy_schema(schema, style))
     416            if verbosity >= 1:
     417                print "Schema %s destroyed" % schema
     418
     419    def _get_schemas(self, apps):
     420        from django.db import models
     421        schemas = set()
     422        for app in apps:
     423            app_models = models.get_models(app)
     424            for model in app_models:
     425                schema = model._meta.db_schema
     426                if not schema or schema in schemas:
     427                    continue
     428                schemas.add(schema)
     429        return schemas
     430
     431    def _get_app_with_schemas(self):
     432        from django.db import models
     433        apps = models.get_apps()
     434        schema_apps = set()
     435        for app in apps:
     436            app_models = models.get_models(app)
     437            for model in app_models:
     438                schema = model._meta.db_schema
     439                if not schema or app in schema_apps:
     440                    continue
     441                schema_apps.add(app)
     442        return schema_apps
     443
     444    def _create_test_db(self, verbosity, autoclobber, schemas):
    367445        "Internal implementation - creates the test db tables."
    368446        suffix = self.sql_table_creation_suffix()
    369447
     
    389467                try:
    390468                    if verbosity >= 1:
    391469                        print "Destroying old test database..."
     470                    self._destroy_test_schemas(verbosity, schemas, cursor)
    392471                    cursor.execute("DROP DATABASE %s" % qn(test_database_name))
    393472                    if verbosity >= 1:
    394473                        print "Creating test database..."
  • django/db/backends/mysql/base.py

    diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
    a b  
    173173            return name # Quoting once is enough.
    174174        return "`%s`" % name
    175175
     176    def prep_db_table(self, db_schema, db_table):
     177        qn = self.quote_name
     178        if db_schema:
     179            return "%s.%s" % (qn(db_schema), qn(db_table))
     180        else:
     181            return qn(db_table)
     182
     183    def prep_db_index(self, db_schema, db_index):
     184        return self.prep_db_table(db_schema, db_index)
     185
    176186    def random_function_sql(self):
    177187        return 'RAND()'
    178188
     
    182192        # to clear all tables of all data
    183193        if tables:
    184194            sql = ['SET FOREIGN_KEY_CHECKS = 0;']
    185             for table in tables:
    186                 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
     195            for (schema, table) in tables:
     196                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
    187197            sql.append('SET FOREIGN_KEY_CHECKS = 1;')
    188198
    189199            # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
    190200            # to reset sequence indices
    191             sql.extend(["%s %s %s %s %s;" % \
    192                 (style.SQL_KEYWORD('ALTER'),
    193                  style.SQL_KEYWORD('TABLE'),
    194                  style.SQL_TABLE(self.quote_name(sequence['table'])),
    195                  style.SQL_KEYWORD('AUTO_INCREMENT'),
    196                  style.SQL_FIELD('= 1'),
    197                 ) for sequence in sequences])
     201            for sequence_info in sequences:
     202                schema_name = sequence_info['schema']
     203                table_name = self.prep_db_table(schema_name, sequence_info['table'])
     204                sql.append("%s %s %s %s %s;" % \
     205                           (style.SQL_KEYWORD('ALTER'),
     206                            style.SQL_KEYWORD('TABLE'),
     207                            style.SQL_TABLE(table_name),
     208                            style.SQL_KEYWORD('AUTO_INCREMENT'),
     209                            style.SQL_FIELD('= 1'),
     210                            ))
    198211            return sql
    199212        else:
    200213            return []
  • django/db/backends/mysql/creation.py

    diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
    a b  
    6363                field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
    6464            ]
    6565        return table_output, deferred
     66
     67    def default_schema(self):
     68        return settings.DATABASE_NAME
     69
     70    def sql_create_schema(self, schema, style):
     71        """
     72        Returns the SQL required to create a single schema.
     73        In MySQL schemas are synonymous to databases
     74        """
     75        qn = self.connection.ops.quote_name
     76        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
     77        return output
     78
     79    def sql_destroy_schema(self, schema, style):
     80        """"
     81        Returns the SQL required to destroy a single schema.
     82        """
     83        qn = self.connection.ops.quote_name
     84        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
     85        return output
  • django/db/backends/mysql/introspection.py

    diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
    a b  
    3333        cursor.execute("SHOW TABLES")
    3434        return [row[0] for row in cursor.fetchall()]
    3535
     36    def get_schema_list(self, cursor):
     37        cursor.execute("SHOW SCHEMAS")
     38        return [row[0] for row in cursor.fetchall()]
     39
     40    def get_schema_table_list(self, cursor, schema):
     41        cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema))
     42        return [schema + "." + row[0] for row in cursor.fetchall()]
     43
    3644    def get_table_description(self, cursor, table_name):
    3745        "Returns a description of the table, with the DB-API cursor.description interface."
    3846        cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  • django/db/backends/oracle/base.py

    diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
    a b  
    5555class DatabaseOperations(BaseDatabaseOperations):
    5656    compiler_module = "django.db.backends.oracle.compiler"
    5757
    58     def autoinc_sql(self, table, column):
     58    def autoinc_sql(self, schema, table, column):
    5959        # To simulate auto-incrementing primary keys in Oracle, we have to
    6060        # create a sequence and a trigger.
    6161        sq_name = get_sequence_name(table)
    6262        tr_name = get_trigger_name(table)
    63         tbl_name = self.quote_name(table)
     63        tbl_name = self.prep_db_table(schema, table)
     64        sq_qname = self.prep_db_table(schema, sq_name)
     65        tr_qname = self.prep_db_table(schema, tr_name)
    6466        col_name = self.quote_name(column)
    6567        sequence_sql = """
    6668DECLARE
     
    6971    SELECT COUNT(*) INTO i FROM USER_CATALOG
    7072        WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
    7173    IF i = 0 THEN
    72         EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
     74        EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s';
    7375    END IF;
    7476END;
    7577/""" % locals()
    7678        trigger_sql = """
    77 CREATE OR REPLACE TRIGGER "%(tr_name)s"
     79CREATE OR REPLACE TRIGGER %(tr_qname)s
    7880BEFORE INSERT ON %(tbl_name)s
    7981FOR EACH ROW
    8082WHEN (new.%(col_name)s IS NULL)
    8183    BEGIN
    82         SELECT "%(sq_name)s".nextval
     84        SELECT %(sq_qname)s.nextval
    8385        INTO :new.%(col_name)s FROM dual;
    8486    END;
    8587/""" % locals()
     
    156158    def deferrable_sql(self):
    157159        return " DEFERRABLE INITIALLY DEFERRED"
    158160
    159     def drop_sequence_sql(self, table):
    160         return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
     161    def drop_sequence_sql(self, schema, table):
     162        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
     163        return "DROP SEQUENCE %s;" % sequence_name
    161164
    162165    def fetch_returned_insert_id(self, cursor):
    163166        return long(cursor._insert_id_var.getvalue())
     
    168171        else:
    169172            return "%s"
    170173
    171     def last_insert_id(self, cursor, table_name, pk_name):
    172         sq_name = get_sequence_name(table_name)
    173         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
     174    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     175        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
     176        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
    174177        return cursor.fetchone()[0]
    175178
    176179    def lookup_cast(self, lookup_type):
     
    181184    def max_name_length(self):
    182185        return 30
    183186
     187    def prep_db_table(self, db_schema, db_table):
     188        qn = self.quote_name
     189        if db_schema:
     190            return "%s.%s" % (qn(db_schema), qn(db_table))
     191        else:
     192            return qn(db_table)
     193
     194    def prep_db_index(self, db_schema, db_index):
     195        return self.prep_db_table(db_schema, db_index)
     196
    184197    def prep_for_iexact_query(self, x):
    185198        return x
    186199
     
    231244    def sql_flush(self, style, tables, sequences):
    232245        # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
    233246        # 'TRUNCATE z;'... style SQL statements
     247        sql = []
    234248        if tables:
    235249            # Oracle does support TRUNCATE, but it seems to get us into
    236250            # FK referential trouble, whereas DELETE FROM table works.
    237             sql = ['%s %s %s;' % \
    238                     (style.SQL_KEYWORD('DELETE'),
    239                      style.SQL_KEYWORD('FROM'),
    240                      style.SQL_FIELD(self.quote_name(table)))
    241                     for table in tables]
     251            for schema, table in tables:
     252                table = self.prep_db_table(schema, table)
     253                sql.append('%s %s %s;' % \
     254                           (style.SQL_KEYWORD('DELETE'),
     255                            style.SQL_KEYWORD('FROM'),
     256                            style.SQL_FIELD(table)))
    242257            # Since we've just deleted all the rows, running our sequence
    243258            # ALTER code will reset the sequence to 0.
    244259            for sequence_info in sequences:
    245                 sequence_name = get_sequence_name(sequence_info['table'])
    246                 table_name = self.quote_name(sequence_info['table'])
     260                schema_name = sequence_info['schema']
     261                sequence_name = self.prep_db_table(schema_name,
     262                                    get_sequence_name(sequence_info['table']))
     263                table_name = self.prep_db_table(schema_name,
     264                                                sequence_info['table'])
    247265                column_name = self.quote_name(sequence_info['column'] or 'id')
    248266                query = _get_sequence_reset_sql() % {'sequence': sequence_name,
    249267                                                     'table': table_name,
    250268                                                     'column': column_name}
    251269                sql.append(query)
    252             return sql
    253         else:
    254             return []
     270        return sql
    255271
    256272    def sequence_reset_sql(self, style, model_list):
    257273        from django.db import models
     
    260276        for model in model_list:
    261277            for f in model._meta.local_fields:
    262278                if isinstance(f, models.AutoField):
    263                     table_name = self.quote_name(model._meta.db_table)
    264                     sequence_name = get_sequence_name(model._meta.db_table)
     279                    table_name = model._meta.qualified_name
     280                    sequence_name = self.prep_db_table(model._meta.db_schema,
     281                                       get_sequence_name(model._meta.db_table))
    265282                    column_name = self.quote_name(f.column)
    266283                    output.append(query % {'sequence': sequence_name,
    267284                                           'table': table_name,
     
    271288                    break
    272289            for f in model._meta.many_to_many:
    273290                if not f.rel.through:
    274                     table_name = self.quote_name(f.m2m_db_table())
    275                     sequence_name = get_sequence_name(f.m2m_db_table())
     291                    table_name = self.quote_name(f.m2m_qualified_name())
     292                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     293                                           get_sequence_name(f.m2m_db_table()))
    276294                    column_name = self.quote_name('id')
    277295                    output.append(query % {'sequence': sequence_name,
    278296                                           'table': table_name,
     
    646664BEGIN
    647665    LOCK TABLE %(table)s IN SHARE MODE;
    648666    SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
    649     SELECT "%(sequence)s".nextval INTO cval FROM dual;
     667    SELECT %(sequence)s.nextval INTO cval FROM dual;
    650668    cval := startvalue - cval;
    651669    IF cval != 0 THEN
    652         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
    653         SELECT "%(sequence)s".nextval INTO cval FROM dual;
    654         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
     670        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
     671        SELECT %(sequence)s.nextval INTO cval FROM dual;
     672        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
    655673    END IF;
    656674    COMMIT;
    657675END;
  • django/db/backends/oracle/creation.py

    diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
    a b  
    3939        'URLField':                     'VARCHAR2(%(max_length)s)',
    4040    }
    4141
     42    def sql_create_schema(self, schema, style, password=None,
     43                          tablespace=None, temp_tablespace=None):
     44        qn = self.connection.ops.quote_name
     45        lock_account = (password is None)
     46        if lock_account:
     47            password = schema
     48        output = []
     49        output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'),
     50                                       qn(schema),
     51                                       style.SQL_KEYWORD('IDENTIFIED BY'),
     52                                       qn(password)))
     53        if tablespace:
     54            output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'),
     55                                     qn(tablespace)))
     56        if temp_tablespace:
     57            output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'),
     58                                     qn(temp_tablespace)))
     59        if lock_account:
     60            output.append(style.SQL_KEYWORD('ACCOUNT LOCK'))
     61        return '\n'.join(output)
     62
     63    def sql_destroy_schema(self, schema, style):
     64        qn = self.connection.ops.quote_name
     65        return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema),
     66                             style.SQL_KEYWORD('CASCADE'))
     67
    4268    remember = {}
    4369
    44     def _create_test_db(self, verbosity=1, autoclobber=False):
     70    def _create_test_db(self, verbosity=1, autoclobber=False, schema):
    4571        TEST_NAME = self._test_database_name()
    4672        TEST_USER = self._test_database_user()
    4773        TEST_PASSWD = self._test_database_passwd()
     
    174200               DEFAULT TABLESPACE %(tblspace)s
    175201               TEMPORARY TABLESPACE %(tblspace_temp)s
    176202            """,
    177             """GRANT CONNECT, RESOURCE TO %(user)s""",
     203            """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""",
    178204        ]
    179205        self._execute_statements(cursor, statements, parameters, verbosity)
    180206
  • django/db/backends/oracle/introspection.py

    diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
    a b  
    119119        for row in cursor.fetchall():
    120120            indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
    121121        return indexes
     122
     123    def schema_name_converter(self, name):
     124        """Convert to lowercase for case-sensitive schema name comparison."""
     125        return name.lower()
     126
     127    def get_schema_list(self, cursor):
     128        "Returns a list of schemas that exist in the database."
     129        sql = """
     130        select distinct username
     131        from all_users, all_objects
     132        where username = owner
     133        """
     134        cursor.execute(sql)
     135        return [schema.lower() for (schema,) in cursor]
     136
     137    def get_schema_table_list(self, cursor, schema):
     138        "Returns a list of tables in a specific schema."
     139        sql = """
     140        select table_name from all_tables
     141        where owner = upper(%s)
     142        """
     143        cursor.execute(sql, [schema])
     144        return [table.lower() for (table,) in cursor]
  • django/db/backends/postgresql/base.py

    diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
    a b  
    8080
    8181class DatabaseFeatures(BaseDatabaseFeatures):
    8282    uses_savepoints = True
     83    default_schema_name = u'public'
    8384
    8485class DatabaseWrapper(BaseDatabaseWrapper):
    8586    operators = {
  • django/db/backends/postgresql/creation.py

    diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
    a b  
    5151                tablespace_sql = ''
    5252
    5353            def get_index_sql(index_name, opclass=''):
     54                index_name = truncate_name(index_name, self.connection.ops.max_name_length())
     55                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    5456                return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    55                         style.SQL_TABLE(qn(truncate_name(index_name,self.connection.ops.max_name_length()))) + ' ' +
     57                        style.SQL_TABLE(index_name) + ' ' +
    5658                        style.SQL_KEYWORD('ON') + ' ' +
    57                         style.SQL_TABLE(qn(db_table)) + ' ' +
     59                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    5860                        "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
    5961                        "%s;" % tablespace_sql)
    6062
  • django/db/backends/postgresql/introspection.py

    diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
    a b  
    3131                AND pg_catalog.pg_table_is_visible(c.oid)""")
    3232        return [row[0] for row in cursor.fetchall()]
    3333
     34    def get_schema_list(self, cursor):
     35        cursor.execute("""
     36            SELECT DISTINCT n.nspname
     37            FROM pg_catalog.pg_class c
     38            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
     39            WHERE c.relkind IN ('r', 'v', '')
     40            AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')""")
     41        return [row[0] for row in cursor.fetchall()]
     42
     43    def get_schema_table_list(self, cursor, schema):
     44        cursor.execute("""
     45            SELECT c.relname
     46            FROM pg_catalog.pg_class c
     47            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
     48            WHERE c.relkind IN ('r', 'v', '')
     49                AND n.nspname = '%s'""" % schema)
     50        return [row[0] for row in cursor.fetchall()]
     51
    3452    def get_table_description(self, cursor, table_name):
    3553        "Returns a description of the table, with the DB-API cursor.description interface."
    3654        cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  • django/db/backends/postgresql/operations.py

    diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
    a b  
    5353            return 'HOST(%s)'
    5454        return '%s'
    5555
    56     def last_insert_id(self, cursor, table_name, pk_name):
     56    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
    5757        # Use pg_get_serial_sequence to get the underlying sequence name
    5858        # from the table name and column name (available since PostgreSQL 8)
     59        table_name = self.prep_db_table(schema_name, table_name)
    5960        cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % (table_name, pk_name))
    6061        return cursor.fetchone()[0]
    6162
     
    6768            return name # Quoting once is enough.
    6869        return '"%s"' % name
    6970
     71    def prep_db_table(self, db_schema, db_table):
     72        qn = self.quote_name
     73        if db_schema:
     74            return "%s.%s" % (qn(db_schema), qn(db_table))
     75        else:
     76            return qn(db_table)
     77
    7078    def sql_flush(self, style, tables, sequences):
    7179        if tables:
     80            qnames = [self.prep_db_table(schema, table)
     81                      for (schema, table) in tables]
    7282            if self.postgres_version[0:2] >= (8,1):
    7383                # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
    7484                # in order to be able to truncate tables referenced by a foreign
     
    7686                # statement.
    7787                sql = ['%s %s;' % \
    7888                    (style.SQL_KEYWORD('TRUNCATE'),
    79                      style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
     89                     style.SQL_FIELD(', '.join(qnames))
    8090                )]
    8191            else:
    8292                # Older versions of Postgres can't do TRUNCATE in a single call, so
     
    8494                sql = ['%s %s %s;' % \
    8595                        (style.SQL_KEYWORD('DELETE'),
    8696                         style.SQL_KEYWORD('FROM'),
    87                          style.SQL_FIELD(self.quote_name(table))
    88                          ) for table in tables]
     97                         style.SQL_FIELD(qname)
     98                         ) for qname in qnames]
    8999
    90100            # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
    91101            # to reset sequence indices
    92102            for sequence_info in sequences:
     103                schema_name = sequence_info['schema']
    93104                table_name = sequence_info['table']
    94105                column_name = sequence_info['column']
    95106                if not (column_name and len(column_name) > 0):
    96107                    # This will be the case if it's an m2m using an autogenerated
    97108                    # intermediate table (see BaseDatabaseIntrospection.sequence_list)
    98109                    column_name = 'id'
     110                table_name = self.prep_db_table(schema_name, table_name)
    99111                sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \
    100112                    (style.SQL_KEYWORD('SELECT'),
    101113                    style.SQL_TABLE(table_name),
     
    118130
    119131            for f in model._meta.local_fields:
    120132                if isinstance(f, models.AutoField):
     133                    table_name = self.prep_db_table(model._meta.db_schema, model._meta.db_table) # XXX: generic schemas support.
    121134                    output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    122135                        (style.SQL_KEYWORD('SELECT'),
    123                         style.SQL_TABLE(model._meta.db_table),
    124                         style.SQL_FIELD(f.column),
     136                        style.SQL_TABLE(table_name),
     137                        style.SQL_FIELD(f.column), # XXX: Do we need qn() here?
    125138                        style.SQL_FIELD(qn(f.column)),
    126139                        style.SQL_FIELD(qn(f.column)),
    127140                        style.SQL_KEYWORD('IS NOT'),
    128141                        style.SQL_KEYWORD('FROM'),
    129                         style.SQL_TABLE(qn(model._meta.db_table))))
     142                        style.SQL_TABLE(model._meta.qualified_name)))
    130143                    break # Only one AutoField is allowed per model, so don't bother continuing.
    131144            for f in model._meta.many_to_many:
    132145                if not f.rel.through:
     146                    table_name = self.prep_db_table(f.m2m_db_schema(), f.m2m_db_table()) # XXX: Do we need qn(f.m2m_db_table()) here? / XXX: generic schemas support.
    133147                    output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    134148                        (style.SQL_KEYWORD('SELECT'),
    135                         style.SQL_TABLE(model._meta.db_table),
    136                         style.SQL_FIELD('id'),
     149                        style.SQL_TABLE(table_name),
     150                        style.SQL_FIELD('id'), # XXX: Do we need qn() here?
    137151                        style.SQL_FIELD(qn('id')),
    138152                        style.SQL_FIELD(qn('id')),
    139153                        style.SQL_KEYWORD('IS NOT'),
    140154                        style.SQL_KEYWORD('FROM'),
    141                         style.SQL_TABLE(qn(f.m2m_db_table()))))
     155                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
    142156        return output
    143157
    144158    def savepoint_create_sql(self, sid):
  • django/db/backends/postgresql_psycopg2/base.py

    diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
    a b  
    6767class DatabaseFeatures(BaseDatabaseFeatures):
    6868    needs_datetime_string_cast = False
    6969    can_return_id_from_insert = False
     70    default_schema_name = u'public'
    7071
    7172class DatabaseOperations(PostgresqlDatabaseOperations):
    7273    def last_executed_query(self, cursor, sql, params):
  • django/db/backends/sqlite3/base.py

    diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
    a b  
    9898                (style.SQL_KEYWORD('DELETE'),
    9999                 style.SQL_KEYWORD('FROM'),
    100100                 style.SQL_FIELD(self.quote_name(table))
    101                  ) for table in tables]
     101                 ) for (_, table) in tables]
    102102        # Note: No requirement for reset of auto-incremented indices (cf. other
    103103        # sql_flush() implementations). Just return SQL at this point
    104104        return sql
  • django/db/backends/sqlite3/creation.py

    diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
    a b  
    3838        "SQLite3 doesn't support constraints"
    3939        return []
    4040
    41     def _create_test_db(self, verbosity, autoclobber):
     41    def _create_test_db(self, verbosity, autoclobber, schemas):
    4242        test_database_name = self.connection.settings_dict['TEST_NAME']
    4343        if test_database_name and test_database_name != ":memory:":
    4444            # Erase the old test database
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    872872        if isinstance(self.rel.to, basestring):
    873873            target = self.rel.to
    874874        else:
    875             target = self.rel.to._meta.db_table
     875            target = self.rel.to._meta.qualified_name
    876876        cls._meta.duplicate_targets[self.column] = (target, "o2m")
    877877
    878878    def contribute_to_related_class(self, cls, related):
     
    961961        to = to.lower()
    962962    meta = type('Meta', (object,), {
    963963        'db_table': field._get_m2m_db_table(klass._meta),
     964        'db_schema': field._get_m2m_db_schema(klass._meta),
    964965        'managed': managed,
    965966        'auto_created': klass,
    966967        'app_label': klass._meta.app_label,
     
    992993            through=kwargs.pop('through', None))
    993994
    994995        self.db_table = kwargs.pop('db_table', None)
     996        self.db_schema = kwargs.pop('db_schema', '')
    995997        if kwargs['rel'].through is not None:
    996998            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    997999
     
    10131015            return util.truncate_name('%s_%s' % (opts.db_table, self.name),
    10141016                                      connection.ops.max_name_length())
    10151017
     1018    def _get_m2m_db_schema(self, opts):
     1019        "Function that can be curried to provide the m2m schema name for this relation"
     1020        if self.rel.through is not None and self.rel.through._meta.db_schema:
     1021            return self.rel.through._meta.db_schema
     1022        return self.db_schema
     1023
     1024    def _get_m2m_qualified_name(self, opts):
     1025        "Function that can be curried to provide the qualified m2m table name for this relation"
     1026        schema = self._get_m2m_db_schema(opts)
     1027        table = self._get_m2m_db_table(opts)
     1028        return connection.ops.prep_db_table(schema, table)
     1029
    10161030    def _get_m2m_attr(self, related, attr):
    10171031        "Function that can be curried to provide the source accessor or DB column name for the m2m table"
    10181032        cache_attr = '_m2m_%s_cache' % attr
     
    11021116
    11031117        # Set up the accessor for the m2m table name for the relation
    11041118        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
     1119        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
     1120        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
     1121                                        cls._meta)
    11051122
    11061123        # Populate some necessary rel arguments so that cross-app relations
    11071124        # work correctly.
     
    11131130        if isinstance(self.rel.to, basestring):
    11141131            target = self.rel.to
    11151132        else:
    1116             target = self.rel.to._meta.db_table
     1133            target = self.rel.to._meta.qualified_name
    11171134        cls._meta.duplicate_targets[self.column] = (target, "m2m")
    11181135
    11191136    def contribute_to_related_class(self, cls, related):
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    a b  
    1717DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    1818                 'unique_together', 'permissions', 'get_latest_by',
    1919                 'order_with_respect_to', 'app_label', 'db_tablespace',
    20                  'abstract', 'managed', 'proxy', 'auto_created')
     20                 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema')
    2121
    2222class Options(object):
    2323    def __init__(self, meta, app_label=None):
     
    2626        self.module_name, self.verbose_name = None, None
    2727        self.verbose_name_plural = None
    2828        self.db_table = ''
     29        #self.db_schema = settings.DATABASE_SCHEMA
     30        self.db_schema = None
     31        self.qualified_name = ''
    2932        self.ordering = []
    3033        self.unique_together =  []
    3134        self.permissions =  []
     
    5154        self.concrete_managers = []
    5255
    5356    def contribute_to_class(self, cls, name):
    54         from django.db import connection
     57        from django.db import connections, router
    5558        from django.db.backends.util import truncate_name
     59        conn = connections[router.db_for_read(cls)]
    5660
    5761        cls._meta = self
    5862        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
     
    6064        self.object_name = cls.__name__
    6165        self.module_name = self.object_name.lower()
    6266        self.verbose_name = get_verbose_name(self.object_name)
     67        self.db_schema = conn.settings_dict['SCHEMA']
    6368
    6469        # Next, apply any overridden values from 'class Meta'.
    6570        if self.meta:
     
    98103        # If the db_table wasn't provided, use the app_label + module_name.
    99104        if not self.db_table:
    100105            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    101             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
     106            self.db_table = truncate_name(self.db_table, conn.ops.max_name_length())
    102107
     108        # Construct qualified table name.
     109        self.qualified_name = conn.ops.prep_db_table(self.db_schema,
     110                                                           self.db_table)
     111        if self.qualified_name == conn.ops.quote_name(self.db_table):
     112            # If unchanged, the backend doesn't support schemas.
     113            self.db_schema = ''
    103114    def _prepare(self, model):
    104115        if self.order_with_respect_to:
    105116            self.order_with_respect_to = self.get_field(self.order_with_respect_to)
     
    173184        self.pk = target._meta.pk
    174185        self.proxy_for_model = target
    175186        self.db_table = target._meta.db_table
     187        self.db_schema = target._meta.db_schema
     188        self.qualified_name = target._meta.qualified_name
    176189
    177190    def __repr__(self):
    178191        return '<Options for %s>' % self.object_name
  • django/db/models/sql/compiler.py

    diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
    a b  
    2121        might not have all the pieces in place at that time.
    2222        """
    2323        if not self.query.tables:
    24             self.query.join((None, self.query.model._meta.db_table, None, None))
     24            self.query.join((None, self.query.model._meta.qualified_name, None, None))
    2525        if (not self.query.select and self.query.default_cols and not
    2626                self.query.included_inherited_models):
    2727            self.query.setup_inherited_models()
     
    248248                        alias = start_alias
    249249                    else:
    250250                        link_field = opts.get_ancestor_link(model)
    251                         alias = self.query.join((start_alias, model._meta.db_table,
     251                        alias = self.query.join((start_alias, model._meta.qualified_name,
    252252                                link_field.column, model._meta.pk.column))
    253253                    seen[model] = alias
    254254            else:
     
    449449                result.append('%s%s%s' % (connector, qn(name), alias_str))
    450450            first = False
    451451        for t in self.query.extra_tables:
    452             alias, unused = self.query.table_alias(t)
     452            alias, unused = self.query.table_alias(qn(t))
    453453            # Only add the alias if it's not already present (the table_alias()
    454454            # calls increments the refcount, so an alias refcount of one means
    455455            # this is the only reference.
     
    529529            # what "used" specifies).
    530530            avoid = avoid_set.copy()
    531531            dupe_set = orig_dupe_set.copy()
    532             table = f.rel.to._meta.db_table
     532            table = f.rel.to._meta.qualified_name
    533533            promote = nullable or f.null
    534534            if model:
    535535                int_opts = opts
     
    550550                                ()))
    551551                        dupe_set.add((opts, lhs_col))
    552552                    int_opts = int_model._meta
    553                     alias = self.query.join((alias, int_opts.db_table, lhs_col,
     553                    alias = self.query.join((alias, int_opts.qualified_name, lhs_col,
    554554                            int_opts.pk.column), exclusions=used,
    555555                            promote=promote)
    556556                    alias_chain.append(alias)
     
    602602                # what "used" specifies).
    603603                avoid = avoid_set.copy()
    604604                dupe_set = orig_dupe_set.copy()
    605                 table = model._meta.db_table
     605                table = model._meta.qualified_name
    606606
    607607                int_opts = opts
    608608                alias = root_alias
     
    625625                            dupe_set.add((opts, lhs_col))
    626626                        int_opts = int_model._meta
    627627                        alias = self.query.join(
    628                             (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
     628                            (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column),
    629629                            exclusions=used, promote=True, reuse=used
    630630                        )
    631631                        alias_chain.append(alias)
     
    766766        # going to be column names (so we can avoid the extra overhead).
    767767        qn = self.connection.ops.quote_name
    768768        opts = self.query.model._meta
    769         result = ['INSERT INTO %s' % qn(opts.db_table)]
     769        result = ['INSERT INTO %s' % opts.qualified_name]
    770770        result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
    771771        values = [self.placeholder(*v) for v in self.query.values]
    772772        result.append('VALUES (%s)' % ', '.join(values))
    773773        params = self.query.params
    774774        if self.return_id and self.connection.features.can_return_id_from_insert:
    775             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
     775            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
    776776            r_fmt, r_params = self.connection.ops.return_insert_id()
    777777            result.append(r_fmt % col)
    778778            params = params + r_params
     
    786786        if self.connection.features.can_return_id_from_insert:
    787787            return self.connection.ops.fetch_returned_insert_id(cursor)
    788788        return self.connection.ops.last_insert_id(cursor,
    789                 self.query.model._meta.db_table, self.query.model._meta.pk.column)
     789                self.query.model._meta.db_schema, self.query.model._meta.db_table,
     790                self.query.model._meta.pk.column)
    790791
    791792
    792793class SQLDeleteCompiler(SQLCompiler):
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    a b  
    593593        Callback used by deferred_to_columns(). The "target" parameter should
    594594        be a set instance.
    595595        """
    596         table = model._meta.db_table
     596        table = model._meta.qualified_name
    597597        if table not in target:
    598598            target[table] = set()
    599599        for field in fields:
     
    775775            alias = self.tables[0]
    776776            self.ref_alias(alias)
    777777        else:
    778             alias = self.join((None, self.model._meta.db_table, None, None))
     778            alias = self.join((None, self.model._meta.qualified_name,
     779                               None, None))
    779780        return alias
    780781
    781782    def count_active_tables(self):
     
    888889                    seen[model] = root_alias
    889890                else:
    890891                    link_field = opts.get_ancestor_link(model)
    891                     seen[model] = self.join((root_alias, model._meta.db_table,
     892                    seen[model] = self.join((root_alias,
     893                            model._meta.qualified_name,
    892894                            link_field.column, model._meta.pk.column))
    893895        self.included_inherited_models = seen
    894896
     
    12131215                                    (id(opts), lhs_col), ()))
    12141216                            dupe_set.add((opts, lhs_col))
    12151217                        opts = int_model._meta
    1216                         alias = self.join((alias, opts.db_table, lhs_col,
    1217                                 opts.pk.column), exclusions=exclusions)
     1218                        alias = self.join((alias, opts.qualified_name,
     1219                                           lhs_col, opts.pk.column),
     1220                                          exclusions=exclusions)
    12181221                        joins.append(alias)
    12191222                        exclusions.add(alias)
    12201223                        for (dupe_opts, dupe_col) in dupe_set:
     
    12391242                        (table1, from_col1, to_col1, table2, from_col2,
    12401243                                to_col2, opts, target) = cached_data
    12411244                    else:
    1242                         table1 = field.m2m_db_table()
     1245                        table1 = field.m2m_qualified_name()
    12431246                        from_col1 = opts.pk.column
    12441247                        to_col1 = field.m2m_column_name()
    12451248                        opts = field.rel.to._meta
    1246                         table2 = opts.db_table
     1249                        table2 = opts.qualified_name
    12471250                        from_col2 = field.m2m_reverse_name()
    12481251                        to_col2 = opts.pk.column
    12491252                        target = opts.pk
     
    12701273                    else:
    12711274                        opts = field.rel.to._meta
    12721275                        target = field.rel.get_related_field()
    1273                         table = opts.db_table
     1276                        table = opts.qualified_name
    12741277                        from_col = field.column
    12751278                        to_col = target.column
    12761279                        orig_opts._join_cache[name] = (table, from_col, to_col,
     
    12921295                        (table1, from_col1, to_col1, table2, from_col2,
    12931296                                to_col2, opts, target) = cached_data
    12941297                    else:
    1295                         table1 = field.m2m_db_table()
     1298                        table1 = field.m2m_qualified_name()
    12961299                        from_col1 = opts.pk.column
    12971300                        to_col1 = field.m2m_reverse_name()
    12981301                        opts = orig_field.opts
    1299                         table2 = opts.db_table
     1302                        table2 = opts.qualified_name
    13001303                        from_col2 = field.m2m_column_name()
    13011304                        to_col2 = opts.pk.column
    13021305                        target = opts.pk
     
    13191322                        local_field = opts.get_field_by_name(
    13201323                                field.rel.field_name)[0]
    13211324                        opts = orig_field.opts
    1322                         table = opts.db_table
     1325                        table = opts.qualified_name
    13231326                        from_col = local_field.column
    13241327                        to_col = field.column
    13251328                        target = opts.pk
     
    15751578        else:
    15761579            opts = self.model._meta
    15771580            if not self.select:
    1578                 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
     1581                count = self.aggregates_module.Count((self.join((None,
     1582                           opts.qualified_name, None, None)), opts.pk.column),
    15791583                                         is_summary=True, distinct=True)
    15801584            else:
    15811585                # Because of SQL portability issues, multi-column, distinct
  • django/db/models/sql/subqueries.py

    diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
    a b  
    3838            field = self.model._meta.pk
    3939            where.add((Constraint(None, field.column, field), 'in',
    4040                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    41             self.do_query(self.model._meta.db_table, where, using=using)
     41            self.do_query(self.model._meta.qualified_name, where, using=using)
    4242
    4343class UpdateQuery(Query):
    4444    """
  • django/db/utils.py

    diff --git a/django/db/utils.py b/django/db/utils.py
    a b  
    7979        conn.setdefault('TEST_NAME', None)
    8080        conn.setdefault('TEST_MIRROR', None)
    8181        conn.setdefault('TIME_ZONE', settings.TIME_ZONE)
    82         for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'):
     82        for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT', 'SCHEMA'):
    8383            conn.setdefault(setting, '')
    8484
    8585    def __getitem__(self, alias):
  • docs/ref/models/options.txt

    diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
    a b  
    6262aren't allowed in Python variable names -- notably, the hyphen -- that's OK.
    6363Django quotes column and table names behind the scenes.
    6464
     65.. _db_schema:
     66
     67``db_schema``
     68-------------
     69
     70.. attribute:: Options.db_schema
     71
     72.. versionadded:: 1.3
     73
     74The name of the database schema to use for the model. If the backend
     75doesn't support multiple schemas, this option is ignored.
     76
     77If this is used then Django will prefix the model table names with the schema
     78name. For example with MySQL Django would use ``db_schema + '.' + db_table``.
     79Be aware that PostgreSQL supports different schemas within the database.
     80MySQL solves the same thing by treating it as just another database.
     81
    6582``db_tablespace``
    6683-----------------
    6784
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    a b  
    317317The port to use when connecting to the database. An empty string means the
    318318default port. Not used with SQLite.
    319319
     320.. setting:: SCHEMA
     321
     322SCHEMA
     323~~~~~~
     324
     325.. versionadded:: 1.3
     326
     327Default: ``''`` (Empty string)
     328
     329The name of the database schema to use for models. If the backend
     330doesn't support multiple schemas, this option is ignored. An empty
     331string means the default schema.
     332
     333If this is used then Django will prefix any table names with the schema name.
     334The schema can be overriden on a per-model basis, for details see
     335:ref:`db_schema`.
     336
    320337.. setting:: USER
    321338
    322339USER
  • docs/topics/db/models.txt

    diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
    a b  
    629629            verbose_name_plural = "oxen"
    630630
    631631Model metadata is "anything that's not a field", such as ordering options
    632 (:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`), or
     632(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`),
     633custom schema for the tables (:attr:`~Options.db_schema`), or
    633634human-readable singular and plural names (:attr:`~Options.verbose_name` and
    634635:attr:`~Options.verbose_name_plural`). None are required, and adding ``class
    635636Meta`` to a model is completely optional.
  • new file tests/modeltests/schemas/__init__.py

    diff --git a/tests/modeltests/schemas/__init__.py b/tests/modeltests/schemas/__init__.py
    new file mode 100644
    - +  
     1#
  • new file tests/modeltests/schemas/fixtures/schema_tests_fixtures.json

    diff --git a/tests/modeltests/schemas/fixtures/schema_tests_fixtures.json b/tests/modeltests/schemas/fixtures/schema_tests_fixtures.json
    new file mode 100644
    - +  
     1[
     2    {
     3        "pk": 1,
     4        "model": "schemas.tag",
     5        "fields": {
     6            "name": "Test"
     7        }
     8    },
     9    {
     10        "pk": 1,
     11        "model": "schemas.blog",
     12        "fields": {
     13            "name":"Test"
     14        }
     15    },
     16    {
     17        "pk": 1,
     18        "model": "schemas.entry",
     19        "fields": {
     20            "blog": 1,
     21            "title": "Test entry",
     22            "tags": [1]
     23        }
     24    },
     25    {
     26        "pk": 1,
     27        "model": "schemas.comment",
     28        "fields": {
     29            "entry": 1,
     30            "text": "nice entry"
     31        }
     32    },
     33    {
     34        "pk": 2,
     35        "model": "schemas.comment",
     36        "fields": {
     37            "entry": 1,
     38            "text": "typical spam comment"
     39        }
     40    }
     41
     42
     43]
  • new file tests/modeltests/schemas/models.py

    diff --git a/tests/modeltests/schemas/models.py b/tests/modeltests/schemas/models.py
    new file mode 100644
    - +  
     1# coding: utf-8
     2
     3from django.db import models
     4
     5class Category(models.Model):
     6    name = models.CharField(max_length=50)
     7
     8
     9class Blog(models.Model):
     10    "Model in default schema"
     11    name = models.CharField(max_length=50)
     12
     13
     14class Entry(models.Model):
     15    "Model in custom schema that references the default"
     16    blog = models.ForeignKey(Blog)
     17    title = models.CharField(max_length=50)
     18    categories = models.ManyToManyField(Category)
     19
     20    class Meta:
     21        # Using custom db_table as well
     22        db_table='schema_blog_entries'
     23        db_schema = 'test_schema'
     24
     25
     26class Comment(models.Model):
     27    "Model in the custom schema that references Entry in the same schema"
     28    entry = models.ForeignKey(Entry)
     29    text = models.CharField(max_length=50)
     30
     31    class Meta:
     32        db_schema = 'test_schema'
     33
     34
     35class Tag(models.Model):
     36    "Used to test m2m relations"
     37    name=models.CharField(max_length=20)
     38    entries=models.ManyToManyField(Entry)
  • new file tests/modeltests/schemas/tests.py

    diff --git a/tests/modeltests/schemas/tests.py b/tests/modeltests/schemas/tests.py
    new file mode 100644
    - +  
     1import unittest
     2from models import Blog, Entry, Comment, Tag, Category
     3
     4from django.conf import settings
     5from django.db import connection, models, DEFAULT_DB_ALIAS
     6
     7class BasicSchemaTests(unittest.TestCase):
     8
     9    def setUp(self):
     10        # Test with actual data. Nothing in there yet
     11        self.assertEqual(Blog.objects.count(), 0)
     12
     13        # Create a blog
     14        self.b = Blog(name='Test')
     15        self.b.save()
     16        self.assertEqual(self.b.id, 1)
     17
     18        # Create an entry
     19        self.e = Entry(blog=self.b, title='Test entry')
     20        self.e.save()
     21        self.assertEqual(self.e.id, 1)
     22
     23        # Create comments
     24        c1 = Comment(entry=self.e, text='nice entry')
     25        c1.save()
     26        c2 = Comment(entry=self.e, text='really like it')
     27        c2.save()
     28
     29    def test_schema_support_regression(self):
     30        # Retrieve the stuff again.
     31        b2 = Blog.objects.get(id=self.b.id)
     32        self.assertTrue(self.b==b2)
     33
     34        self.assertEqual(b2.entry_set.count(), 1)
     35        #[<Entry: Entry object>]
     36
     37        # Make sure we don't break many to many relations
     38        t = Tag(name="test")
     39        t.save()
     40        t.entries.add(self.e)
     41
     42        t2 = Tag.objects.get(id=t.id)
     43        self.assertEqual(t2.entries.count(), 1)
     44
     45        # Test if we support schemas and can find the table if so
     46        if self.e._meta.db_schema:
     47            tables = connection.introspection.schema_table_names(self.e._meta.db_schema)
     48        else:
     49            tables = connection.introspection.table_names()
     50        self.assertTrue(connection.introspection.table_name_converter(self.e._meta.db_table) in tables)
     51
     52
     53class SchemaTests(unittest.TestCase):
     54    fixtures = ['schema_tests_fixtures',]
     55    VALID_ENGINES = ['postgresql_psycopg2', 'postgresql', 'mysql', 'oracle']
     56    VALID_ENGINES = ['django.db.backends.%s' %b for b in VALID_ENGINES]
     57
     58    def test_meta_information(self):
     59        e = Entry.objects.get(id=1)
     60        b = Blog.objects.get(id=1)
     61        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in self.VALID_ENGINES:
     62            self.assertEqual('test_schema', e._meta.db_schema)
     63            if settings.DATABASE_SCHEMA:
     64                self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema)
     65            else:
     66                self.assertFalse(b._meta.db_schema)
     67        else:
     68            self.assertFalse(e._meta.db_schema) # No model schema
     69            self.assertFalse(b._meta.db_schema) # No global schema
     70
     71    def test_schema_in_m2m_declaring_model(self):
     72        e = Entry.objects.get(id=1)
     73        c = Category(name='Test')
     74        c.save()
     75        e.categories = [c]
     76
     77        categories = e.categories.filter(name='Test')
     78
     79        self.assertEqual(1, len(categories))
  • tests/regressiontests/backends/tests.py

    diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
    a b  
    124124            VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
    125125            VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
    126126            tables = [
    127                 VLM._meta.db_table,
    128                 VLM_m2m._meta.db_table,
     127                (VLM._meta.db_schema, VLM._meta.db_table),
     128                (VLM_m2m._meta.db_schema, VLM_m2m._meta.db_table),
    129129            ]
    130130            sequences = [
    131131                {
    132132                    'column': VLM._meta.pk.column,
    133                     'table': VLM._meta.db_table
     133                    'table': VLM._meta.db_table,
     134                    'schema': VLM._meta.db_schema,
    134135                },
    135136            ]
    136137            cursor = connection.cursor()
  • tests/regressiontests/introspection/tests.py

    diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
    a b  
    5858
    5959    def test_sequence_list(self):
    6060        sequences = connection.introspection.sequence_list()
    61         expected = {'table': Reporter._meta.db_table, 'column': 'id'}
     61        expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''}
    6262        self.assert_(expected in sequences,
    6363                     'Reporter sequence not found in sequence_list()')
    6464
Back to Top