Ticket #6148: 6148-generic-schema-support-r12426.diff

File 6148-generic-schema-support-r12426.diff, 73.0 KB (added by Ramiro Morales, 14 years ago)

Patch updated to r12426 (post multi-db merge). Django test suite runs w/o errors with sqlite3 and PostgreSQL.

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    a b  
    139139DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
    140140DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
    141141DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
     142DATABASE_SCHEMA = ''           # Set to empty string for default.
    142143
    143144# New format
    144145DATABASES = {
  • 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  
    5151        cursor = connection.cursor()
    5252
    5353        # Get a list of already installed *models* so that references work right.
    54         tables = connection.introspection.table_names()
     54        tables = [('', tn) for tn in connection.introspection.table_names()]
    5555        seen_models = connection.introspection.installed_models(tables)
     56        seen_schemas = set()
    5657        created_models = set()
    5758        pending_references = {}
    5859
     
    6768        # Create the tables for each model
    6869        for app_name, model_list in manifest.items():
    6970            for model in model_list:
    70                 # Create the model's database table, if it doesn't already exist.
     71                # Add model-defined schema tables if any.
     72                db_schema = model._meta.db_schema
     73                if db_schema:
     74                    db_schema = connection.introspection.schema_name_converter(db_schema)
     75                    if db_schema not in seen_schemas:
     76                        tables += [(db_schema, tn) for tn in
     77                                   connection.introspection.schema_table_names(db_schema)]
     78                        seen_schemas.add(db_schema)
     79
     80                # Create the model's database table,
     81                # if it doesn't already exist.
    7182                if verbosity >= 2:
    7283                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
    7384                opts = model._meta
    74                 if (connection.introspection.table_name_converter(opts.db_table) in tables or
    75                     (opts.auto_created and
    76                     connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)):
     85                schema_table = (db_schema, connection.introspection.table_name_converter(opts.db_table))
     86                if schema_table in tables or \
     87                    (opts.auto_created and \
     88                    (db_schema,
     89                     connection.introspection.table_name_converter(opts.auto_created._meta.db_table))
     90                    in tables):
    7791                    continue
    7892                sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
    7993                seen_models.add(model)
     
    87101                    print "Creating table %s" % model._meta.db_table
    88102                for statement in sql:
    89103                    cursor.execute(statement)
    90                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
     104                tables.append(schema_table)
    91105
    92106
    93107        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  
    7171
    7272    # Figure out which tables already exist
    7373    if cursor:
    74         table_names = connection.introspection.get_table_list(cursor)
     74        table_names = [('', tn) for tn in
     75                       connection.introspection.get_table_list(cursor)]
    7576    else:
    7677        table_names = []
    7778
     
    8283
    8384    references_to_delete = {}
    8485    app_models = models.get_models(app, include_auto_created=True)
     86    seen_schemas = set()
    8587    for model in app_models:
    86         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     88        db_schema = model._meta.db_schema
     89        # Find additional tables in model-defined schemas.
     90        if db_schema:
     91            db_schema = connection.introspection.schema_name_converter(db_schema)
     92            if db_schema not in seen_schemas:
     93                table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)]
     94                seen_schemas.add(db_schema)
     95        schema_table = (db_schema,
     96                        connection.introspection.table_name_converter(model._meta.db_table))
     97
     98        if cursor and schema_table in table_names:
    8799            # The table exists, so it needs to be dropped
    88100            opts = model._meta
    89101            for f in opts.local_fields:
     
    93105            to_delete.add(model)
    94106
    95107    for model in app_models:
    96         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     108        db_schema = model._meta.db_schema
     109        if db_schema:
     110            db_schema = connection.introspection.schema_name_converter(db_schema)
     111        schema_table = (db_schema,
     112                        connection.introspection.table_name_converter(model._meta.db_table))
     113        if schema_table in table_names:
    97114            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
    98115
    99116    # Close database connection explicitly, in case this output is being piped
     
    118135    if only_django:
    119136        tables = connection.introspection.django_table_names(only_existing=True)
    120137    else:
    121         tables = connection.introspection.table_names()
     138        tables = [('', tn) for tn in connection.introspection.table_names()]
    122139    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
    123140    return statements
    124141
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    a b  
    107107    def __init__(self):
    108108        self._cache = {}
    109109
    110     def autoinc_sql(self, table, column):
     110    def autoinc_sql(self, schema, table, column):
    111111        """
    112112        Returns any SQL needed to support auto-incrementing primary keys, or
    113113        None if no SQL is necessary.
     
    153153        """
    154154        return "DROP CONSTRAINT"
    155155
    156     def drop_sequence_sql(self, table):
     156    def drop_sequence_sql(self, schema, table):
    157157        """
    158158        Returns any SQL necessary to drop the sequence for the given table.
    159159        Returns None if no SQL is necessary.
     
    214214
    215215        return smart_unicode(sql) % u_params
    216216
    217     def last_insert_id(self, cursor, table_name, pk_name):
     217    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
    218218        """
    219219        Given a cursor object that has just performed an INSERT statement into
    220220        a table that has an auto-incrementing ID, returns the newly created ID.
     
    288288        """
    289289        raise NotImplementedError()
    290290
     291    def prep_db_table(self, db_schema, db_table):
     292        """
     293        Prepares and formats the table name if necessary.
     294        Just returns quoted db_table if not supported.
     295        """
     296        return self.quote_name(db_table)
     297
     298    def prep_db_index(self, db_schema, db_index):
     299        """
     300        Prepares and formats the table index name if necessary.
     301        Just returns quoted db_index if not supported.
     302        """
     303        return self.quote_name(db_index)
     304
    291305    def random_function_sql(self):
    292306        """
    293307        Returns a SQL expression that returns a random value.
     
    487501        return name
    488502
    489503    def table_names(self):
    490         "Returns a list of names of all tables that exist in the database."
     504        "Returns a list of names of all tables that exist in the default schema."
    491505        cursor = self.connection.cursor()
    492506        return self.get_table_list(cursor)
    493507
     508    def schema_name_converter(self, name):
     509        """Apply a conversion to the name for the purposes of comparison.
     510
     511        The default schema name converter is for case sensitive comparison.
     512        """
     513        return name
     514
     515    def get_schema_list(self, cursor):
     516        "Returns a list of schemas that exist in the database"
     517        return []
     518
     519    def get_schema_table_list(self, cursor, schema):
     520        "Returns a list of tables in a specific schema"
     521        return []
     522
     523    def schema_names(self):
     524        cursor = self.connection.cursor()
     525        return self.get_schema_list(cursor)
     526
     527    def schema_table_names(self, schema):
     528        "Returns a list of names of all tables that exist in the database schema."
     529        cursor = self.connection.cursor()
     530        return self.get_schema_table_list(cursor, schema)
     531
    494532    def django_table_names(self, only_existing=False):
    495533        """
    496         Returns a list of all table names that have associated Django models and
    497         are in INSTALLED_APPS.
     534        Returns a list of tuples containing all schema and table names that
     535        have associated Django models and are in INSTALLED_APPS.
    498536
    499         If only_existing is True, the resulting list will only include the tables
    500         that actually exist in the database.
     537        If only_existing is True, the resulting list will only include the
     538        tables that actually exist in the database.
    501539        """
    502540        from django.db import models
    503541        tables = set()
    504         for app in models.get_apps():
    505             for model in models.get_models(app):
    506                 if not model._meta.managed:
    507                     continue
    508                 tables.add(model._meta.db_table)
    509                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    510542        if only_existing:
    511             tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
     543            existing_tables = set([('', tn) for tn in self.table_names()])
     544            seen_schemas = set()
     545        for model in models.get_models():
     546            if not model._meta.managed:
     547                continue
     548            db_schema = model._meta.db_schema
     549            db_table = model._meta.db_table
     550            if only_existing and db_schema and db_schema not in seen_schemas:
     551                existing_tables.update([(db_schema, tn) for tn in
     552                                        self.schema_table_names(db_schema)])
     553                seen_schemas.add(db_schema)
     554            tables.add((model._meta.db_schema, model._meta.db_table))
     555            for f in model._meta.local_many_to_many:
     556                m2m_schema = f.m2m_db_schema()
     557                m2m_table = f.m2m_db_table()
     558                if only_existing and m2m_schema and m2m_schema not in seen_schemas:
     559                    existing_tables.update([(m2m_schema, tn) for tn in
     560                                        self.schema_table_names(m2m_schema)])
     561                    seen_schemas.add(m2m_schema)
     562                tables.add((m2m_schema, m2m_table))
     563        if only_existing:
     564            tables = [(s, t) for (s, t) in tables
     565                      if (self.schema_name_converter(s),
     566                          self.table_name_converter(t)) in existing_tables]
    512567        return tables
    513568
    514569    def installed_models(self, tables):
     
    535590                    continue
    536591                for f in model._meta.local_fields:
    537592                    if isinstance(f, models.AutoField):
    538                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
     593                        sequence_list.append({'table': model._meta.db_table,
     594                                              'column': f.column,
     595                                              'schema': model._meta.db_schema})
    539596                        break # Only one AutoField is allowed per model, so don't bother continuing.
    540597
    541598                for f in model._meta.local_many_to_many:
     599                    schema = f.m2m_db_schema()
    542600                    # If this is an m2m using an intermediate table,
    543601                    # we don't need to reset the sequence.
    544602                    if f.rel.through is None:
    545                         sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     603                        sequence_list.append({'table': f.m2m_db_table(),
     604                                              'column': None,
     605                                              'schema': schema})
    546606
    547607        return sequence_list
    548608
  • django/db/backends/creation.py

    diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
    a b  
    3232        """
    3333        return '%x' % (abs(hash(args)) % 4294967296L)  # 2**32
    3434
     35    def default_schema(self):
     36        return ""
     37
     38    def sql_create_schema(self, schema, style):
     39        """"
     40        Returns the SQL required to create a single schema
     41        """
     42        qn = self.connection.ops.quote_name
     43        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
     44        return output
     45
    3546    def sql_create_model(self, model, style, known_models=set()):
    3647        """
    3748        Returns the SQL required to create a single model, as a tuple of:
     
    8091            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    8192                ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
    8293
    83         full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     94        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' (']
    8495        for i, line in enumerate(table_output): # Combine and add commas.
    8596            full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    8697        full_statement.append(')')
     
    92103        if opts.has_auto_field:
    93104            # Add any extra SQL needed to support auto-incrementing primary keys.
    94105            auto_column = opts.auto_field.db_column or opts.auto_field.name
    95             autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
     106            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema,
     107                                                          opts.db_table,
     108                                                          auto_column)
    96109            if autoinc_sql:
    97110                for stmt in autoinc_sql:
    98111                    final_output.append(stmt)
     
    104117        qn = self.connection.ops.quote_name
    105118        if field.rel.to in known_models:
    106119            output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
    107                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
     120                style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \
    108121                style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
    109122                self.connection.ops.deferrable_sql()
    110123            ]
     
    130143            for rel_class, f in pending_references[model]:
    131144                rel_opts = rel_class._meta
    132145                r_table = rel_opts.db_table
     146                r_qname = rel_opts.qualified_name
    133147                r_col = f.column
    134148                table = opts.db_table
     149                qname = opts.qualified_name
    135150                col = opts.get_field(f.rel.field_name).column
    136151                # For MySQL, r_name must be unique in the first 64 characters.
    137152                # So we are careful with character usage here.
    138153                r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
    139154                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    140                     (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
    141                     qn(r_col), qn(table), qn(col),
     155                    (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())),
     156                    qn(r_col), qname, qn(col),
    142157                    self.connection.ops.deferrable_sql()))
    143158            del pending_references[model]
    144159        return final_output
     
    169184        from django.db.backends.util import truncate_name
    170185
    171186        output = []
    172         if f.auto_created:
     187        if f.rel.through._meta.auto_created:
    173188            opts = model._meta
    174189            qn = self.connection.ops.quote_name
    175190            tablespace = f.db_tablespace or opts.db_tablespace
     
    182197            else:
    183198                tablespace_sql = ''
    184199            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
    185                 style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     200                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
    186201            table_output.append('    %s %s %s%s,' %
    187202                (style.SQL_FIELD(qn('id')),
    188203                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)),
     
    214229                self.connection.ops.deferrable_sql()))
    215230
    216231            # Add any extra SQL needed to support auto-incrementing PKs
    217             autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
     232            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
     233                                                          f.m2m_db_table(),
     234                                                          'id')
    218235            if autoinc_sql:
    219236                for stmt in autoinc_sql:
    220237                    output.append(stmt)
     
    237254                (style.SQL_FIELD(qn(field.m2m_column_name())),
    238255                style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)),
    239256                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    240                 style.SQL_TABLE(qn(opts.db_table)),
     257                style.SQL_TABLE(opts.qualified_name),
    241258                style.SQL_FIELD(qn(opts.pk.column)),
    242259                self.connection.ops.deferrable_sql()),
    243260            '    %s %s %s %s (%s)%s,' %
    244261                (style.SQL_FIELD(qn(field.m2m_reverse_name())),
    245262                style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)),
    246263                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    247                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
     264                style.SQL_TABLE(field.rel.to._meta.qualified_name),
    248265                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
    249266                self.connection.ops.deferrable_sql())
    250267        ]
     
    274291                    tablespace_sql = ''
    275292            else:
    276293                tablespace_sql = ''
     294            index_name = '%s_%s' % (model._meta.db_table, f.column)
     295            index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    277296            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    278                 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
     297                style.SQL_TABLE(index_name) + ' ' +
    279298                style.SQL_KEYWORD('ON') + ' ' +
    280                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
     299                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    281300                "(%s)" % style.SQL_FIELD(qn(f.column)) +
    282301                "%s;" % tablespace_sql]
    283302        else:
    284303            output = []
    285304        return output
    286305
     306    def sql_destroy_schema(self, schema, style):
     307        """"
     308        Returns the SQL required to destroy a single schema.
     309        """
     310        qn = self.connection.ops.quote_name
     311        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
     312        return output
     313
    287314    def sql_destroy_model(self, model, references_to_delete, style):
    288315        "Return the DROP TABLE and restraint dropping statements for a single model"
    289316        if not model._meta.managed or model._meta.proxy:
     
    291318        # Drop the table now
    292319        qn = self.connection.ops.quote_name
    293320        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
    294                               style.SQL_TABLE(qn(model._meta.db_table)))]
     321                              style.SQL_TABLE(model._meta.qualified_name))]
    295322        if model in references_to_delete:
    296323            output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
    297324
    298325        if model._meta.has_auto_field:
    299             ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
     326            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     327                                                       model._meta.db_table)
    300328            if ds:
    301329                output.append(ds)
    302330        return output
     
    310338        qn = self.connection.ops.quote_name
    311339        for rel_class, f in references_to_delete[model]:
    312340            table = rel_class._meta.db_table
     341            qname = rel_class._meta.qualified_name
    313342            col = f.column
    314343            r_table = model._meta.db_table
    315344            r_col = model._meta.get_field(f.rel.field_name).column
    316345            r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
    317346            output.append('%s %s %s %s;' % \
    318347                (style.SQL_KEYWORD('ALTER TABLE'),
    319                 style.SQL_TABLE(qn(table)),
     348                style.SQL_TABLE(qname),
    320349                style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
    321350                style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
    322351        del references_to_delete[model]
     
    332361
    333362        qn = self.connection.ops.quote_name
    334363        output = []
    335         if f.auto_created:
     364        if f.rel.through._meta.auto_created:
    336365            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
    337                 style.SQL_TABLE(qn(f.m2m_db_table()))))
    338             ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
     366                style.SQL_TABLE(f.m2m_qualified_name())))
     367            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     368                               "%s_%s" % (model._meta.db_table, f.column))
    339369            if ds:
    340370                output.append(ds)
    341371        return output
     
    348378        if verbosity >= 1:
    349379            print "Creating test database '%s'..." % self.connection.alias
    350380
    351         test_database_name = self._create_test_db(verbosity, autoclobber)
     381        schema_apps = self._get_app_with_schemas()
     382        schemas = self._get_schemas(schema_apps)
     383        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
    352384
    353385        self.connection.close()
    354386        self.connection.settings_dict["NAME"] = test_database_name
    355387        can_rollback = self._rollback_works()
    356388        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
    357389
     390        # Create the test schemas.
     391        cursor = self.connection.cursor()
     392        self._create_test_schemas(verbosity, schemas, cursor)
     393
    358394        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
    359395
    360396        if settings.CACHE_BACKEND.startswith('db://'):
     
    368404
    369405        return test_database_name
    370406
    371     def _create_test_db(self, verbosity, autoclobber):
     407    def _create_test_schemas(self, verbosity, schemas, cursor):
     408        from django.core.management.color import no_style
     409        style = no_style()
     410        for schema in schemas:
     411            if verbosity >= 1:
     412                print "Creating schema %s" % schema
     413            cursor.execute(self.sql_create_schema(schema, style))
     414
     415    def _destroy_test_schemas(self, verbosity, schemas, cursor):
     416        from django.core.management.color import no_style
     417        style = no_style()
     418        for schema in schemas:
     419            if verbosity >= 1:
     420                print "Destroying schema %s" % schema
     421            cursor.execute(self.sql_destroy_schema(schema, style))
     422            if verbosity >= 1:
     423                print "Schema %s destroyed" % schema
     424
     425    def _get_schemas(self, apps):
     426        from django.db import models
     427        schemas = set()
     428        for app in apps:
     429            app_models = models.get_models(app)
     430            for model in app_models:
     431                schema = model._meta.db_schema
     432                if not schema or schema in schemas:
     433                    continue
     434                schemas.add(schema)
     435        return schemas
     436
     437    def _get_app_with_schemas(self):
     438        from django.db import models
     439        apps = models.get_apps()
     440        schema_apps = set()
     441        for app in apps:
     442            app_models = models.get_models(app)
     443            for model in app_models:
     444                schema = model._meta.db_schema
     445                if not schema or app in schema_apps:
     446                    continue
     447                schema_apps.add(app)
     448        return schema_apps
     449
     450    def _create_test_db(self, verbosity, autoclobber, schemas):
    372451        "Internal implementation - creates the test db tables."
    373452        suffix = self.sql_table_creation_suffix()
    374453
     
    394473                try:
    395474                    if verbosity >= 1:
    396475                        print "Destroying old test database..."
     476                    self._destroy_test_schemas(verbosity, schemas, cursor)
    397477                    cursor.execute("DROP DATABASE %s" % qn(test_database_name))
    398478                    if verbosity >= 1:
    399479                        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  
    170170            return name # Quoting once is enough.
    171171        return "`%s`" % name
    172172
     173    def prep_db_table(self, db_schema, db_table):
     174        qn = self.quote_name
     175        if db_schema:
     176            return "%s.%s" % (qn(db_schema), qn(db_table))
     177        else:
     178            return qn(db_table)
     179
     180    def prep_db_index(self, db_schema, db_index):
     181        return self.prep_db_table(db_schema, db_index)
     182
    173183    def random_function_sql(self):
    174184        return 'RAND()'
    175185
     
    179189        # to clear all tables of all data
    180190        if tables:
    181191            sql = ['SET FOREIGN_KEY_CHECKS = 0;']
    182             for table in tables:
    183                 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
     192            for (schema, table) in tables:
     193                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
    184194            sql.append('SET FOREIGN_KEY_CHECKS = 1;')
    185195
    186196            # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
    187197            # to reset sequence indices
    188             sql.extend(["%s %s %s %s %s;" % \
    189                 (style.SQL_KEYWORD('ALTER'),
    190                  style.SQL_KEYWORD('TABLE'),
    191                  style.SQL_TABLE(self.quote_name(sequence['table'])),
    192                  style.SQL_KEYWORD('AUTO_INCREMENT'),
    193                  style.SQL_FIELD('= 1'),
    194                 ) for sequence in sequences])
     198            for sequence_info in sequences:
     199                schema_name = sequence_info['schema']
     200                table_name = self.prep_db_table(schema_name, sequence_info['table'])
     201                sql.append("%s %s %s %s %s;" % \
     202                           (style.SQL_KEYWORD('ALTER'),
     203                            style.SQL_KEYWORD('TABLE'),
     204                            style.SQL_TABLE(table_name),
     205                            style.SQL_KEYWORD('AUTO_INCREMENT'),
     206                            style.SQL_FIELD('= 1'),
     207                            ))
    195208            return sql
    196209        else:
    197210            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  
    5757class DatabaseOperations(BaseDatabaseOperations):
    5858    compiler_module = "django.db.backends.oracle.compiler"
    5959
    60     def autoinc_sql(self, table, column):
     60    def autoinc_sql(self, schema, table, column):
    6161        # To simulate auto-incrementing primary keys in Oracle, we have to
    6262        # create a sequence and a trigger.
    6363        sq_name = get_sequence_name(table)
    6464        tr_name = get_trigger_name(table)
    65         tbl_name = self.quote_name(table)
     65        tbl_name = self.prep_db_table(schema, table)
     66        sq_qname = self.prep_db_table(schema, sq_name)
     67        tr_qname = self.prep_db_table(schema, tr_name)
    6668        col_name = self.quote_name(column)
    6769        sequence_sql = """
    6870DECLARE
     
    7173    SELECT COUNT(*) INTO i FROM USER_CATALOG
    7274        WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
    7375    IF i = 0 THEN
    74         EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
     76        EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s';
    7577    END IF;
    7678END;
    7779/""" % locals()
    7880        trigger_sql = """
    79 CREATE OR REPLACE TRIGGER "%(tr_name)s"
     81CREATE OR REPLACE TRIGGER %(tr_qname)s
    8082BEFORE INSERT ON %(tbl_name)s
    8183FOR EACH ROW
    8284WHEN (new.%(col_name)s IS NULL)
    8385    BEGIN
    84         SELECT "%(sq_name)s".nextval
     86        SELECT %(sq_qname)s.nextval
    8587        INTO :new.%(col_name)s FROM dual;
    8688    END;
    8789/""" % locals()
     
    158160    def deferrable_sql(self):
    159161        return " DEFERRABLE INITIALLY DEFERRED"
    160162
    161     def drop_sequence_sql(self, table):
    162         return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
     163    def drop_sequence_sql(self, schema, table):
     164        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
     165        return "DROP SEQUENCE %s;" % sequence_name
    163166
    164167    def fetch_returned_insert_id(self, cursor):
    165168        return long(cursor._insert_id_var.getvalue())
     
    170173        else:
    171174            return "%s"
    172175
    173     def last_insert_id(self, cursor, table_name, pk_name):
    174         sq_name = get_sequence_name(table_name)
    175         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
     176    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     177        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
     178        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
    176179        return cursor.fetchone()[0]
    177180
    178181    def lookup_cast(self, lookup_type):
     
    183186    def max_name_length(self):
    184187        return 30
    185188
     189    def prep_db_table(self, db_schema, db_table):
     190        qn = self.quote_name
     191        if db_schema:
     192            return "%s.%s" % (qn(db_schema), qn(db_table))
     193        else:
     194            return qn(db_table)
     195
     196    def prep_db_index(self, db_schema, db_index):
     197        return self.prep_db_table(db_schema, db_index)
     198
    186199    def prep_for_iexact_query(self, x):
    187200        return x
    188201
     
    233246    def sql_flush(self, style, tables, sequences):
    234247        # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
    235248        # 'TRUNCATE z;'... style SQL statements
     249        sql = []
    236250        if tables:
    237251            # Oracle does support TRUNCATE, but it seems to get us into
    238252            # FK referential trouble, whereas DELETE FROM table works.
    239             sql = ['%s %s %s;' % \
    240                     (style.SQL_KEYWORD('DELETE'),
    241                      style.SQL_KEYWORD('FROM'),
    242                      style.SQL_FIELD(self.quote_name(table)))
    243                     for table in tables]
     253            for schema, table in tables:
     254                table = self.prep_db_table(schema, table)
     255                sql.append('%s %s %s;' % \
     256                           (style.SQL_KEYWORD('DELETE'),
     257                            style.SQL_KEYWORD('FROM'),
     258                            style.SQL_FIELD(table)))
    244259            # Since we've just deleted all the rows, running our sequence
    245260            # ALTER code will reset the sequence to 0.
    246261            for sequence_info in sequences:
    247                 sequence_name = get_sequence_name(sequence_info['table'])
    248                 table_name = self.quote_name(sequence_info['table'])
     262                schema_name = sequence_info['schema']
     263                sequence_name = self.prep_db_table(schema_name,
     264                                    get_sequence_name(sequence_info['table']))
     265                table_name = self.prep_db_table(schema_name,
     266                                                sequence_info['table'])
    249267                column_name = self.quote_name(sequence_info['column'] or 'id')
    250268                query = _get_sequence_reset_sql() % {'sequence': sequence_name,
    251269                                                     'table': table_name,
    252270                                                     'column': column_name}
    253271                sql.append(query)
    254             return sql
    255         else:
    256             return []
     272        return sql
    257273
    258274    def sequence_reset_sql(self, style, model_list):
    259275        from django.db import models
     
    262278        for model in model_list:
    263279            for f in model._meta.local_fields:
    264280                if isinstance(f, models.AutoField):
    265                     table_name = self.quote_name(model._meta.db_table)
    266                     sequence_name = get_sequence_name(model._meta.db_table)
     281                    table_name = model._meta.qualified_name
     282                    sequence_name = self.prep_db_table(model._meta.db_schema,
     283                                       get_sequence_name(model._meta.db_table))
    267284                    column_name = self.quote_name(f.column)
    268285                    output.append(query % {'sequence': sequence_name,
    269286                                           'table': table_name,
     
    273290                    break
    274291            for f in model._meta.many_to_many:
    275292                if not f.rel.through:
    276                     table_name = self.quote_name(f.m2m_db_table())
    277                     sequence_name = get_sequence_name(f.m2m_db_table())
     293                    table_name = self.quote_name(f.m2m_qualified_name())
     294                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     295                                           get_sequence_name(f.m2m_db_table()))
    278296                    column_name = self.quote_name('id')
    279297                    output.append(query % {'sequence': sequence_name,
    280298                                           'table': table_name,
     
    618636BEGIN
    619637    LOCK TABLE %(table)s IN SHARE MODE;
    620638    SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
    621     SELECT "%(sequence)s".nextval INTO cval FROM dual;
     639    SELECT %(sequence)s.nextval INTO cval FROM dual;
    622640    cval := startvalue - cval;
    623641    IF cval != 0 THEN
    624         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
    625         SELECT "%(sequence)s".nextval INTO cval FROM dual;
    626         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
     642        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
     643        SELECT %(sequence)s.nextval INTO cval FROM dual;
     644        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
    627645    END IF;
    628646    COMMIT;
    629647END;
  • 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/creation.py

    diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
    a b  
    5050                tablespace_sql = ''
    5151
    5252            def get_index_sql(index_name, opclass=''):
     53                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    5354                return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    54                         style.SQL_TABLE(qn(index_name)) + ' ' +
     55                        style.SQL_TABLE(index_name) + ' ' +
    5556                        style.SQL_KEYWORD('ON') + ' ' +
    56                         style.SQL_TABLE(qn(db_table)) + ' ' +
     57                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    5758                        "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
    5859                        "%s;" % tablespace_sql)
    5960
  • 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):
    57         cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
     56    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     57        sequence_name = '%s_%s_seq' % (table_name, pk_name)
     58        sequence_name = self.prep_db_table(schema_name, sequence_name)
     59        cursor.execute("SELECT CURRVAL('%s')" % sequence_name)
    5860        return cursor.fetchone()[0]
    5961
    6062    def no_limit_value(self):
     
    6567            return name # Quoting once is enough.
    6668        return '"%s"' % name
    6769
     70    def prep_db_table(self, db_schema, db_table):
     71        qn = self.quote_name
     72        if db_schema:
     73            return "%s.%s" % (qn(db_schema), qn(db_table))
     74        else:
     75            return qn(db_table)
     76
    6877    def sql_flush(self, style, tables, sequences):
    6978        if tables:
     79            qnames = [self.prep_db_table(schema, table)
     80                      for (schema, table) in tables]
    7081            if self.postgres_version[0:2] >= (8,1):
    7182                # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
    7283                # in order to be able to truncate tables referenced by a foreign
     
    7485                # statement.
    7586                sql = ['%s %s;' % \
    7687                    (style.SQL_KEYWORD('TRUNCATE'),
    77                      style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
     88                     style.SQL_FIELD(', '.join(qnames))
    7889                )]
    7990            else:
    8091                # Older versions of Postgres can't do TRUNCATE in a single call, so
     
    8293                sql = ['%s %s %s;' % \
    8394                        (style.SQL_KEYWORD('DELETE'),
    8495                         style.SQL_KEYWORD('FROM'),
    85                          style.SQL_FIELD(self.quote_name(table))
    86                          ) for table in tables]
     96                         style.SQL_FIELD(qname)
     97                         ) for qname in qnames]
    8798
    8899            # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
    89100            # to reset sequence indices
    90101            for sequence_info in sequences:
     102                schema_name = sequence_info['schema']
    91103                table_name = sequence_info['table']
    92104                column_name = sequence_info['column']
    93105                if column_name and len(column_name) > 0:
    94106                    sequence_name = '%s_%s_seq' % (table_name, column_name)
    95107                else:
    96108                    sequence_name = '%s_id_seq' % table_name
     109                sequence_name = self.prep_db_table(schema_name, sequence_name)
    97110                sql.append("%s setval('%s', 1, false);" % \
    98111                    (style.SQL_KEYWORD('SELECT'),
    99                     style.SQL_FIELD(self.quote_name(sequence_name)))
     112                    style.SQL_FIELD(sequence_name))
    100113                )
    101114            return sql
    102115        else:
     
    112125            # if there are records (as the max pk value is already in use), otherwise set it to false.
    113126            for f in model._meta.local_fields:
    114127                if isinstance(f, models.AutoField):
     128                    sequence_name = qn('%s_%s_seq' % (model._meta.db_table,
     129                                                      f.column)) # XXX: generic schemas support
     130                    sequence_name = self.prep_db_table(model._meta.db_schema,
     131                                                       sequence_name)
    115132                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    116133                        (style.SQL_KEYWORD('SELECT'),
    117                         style.SQL_FIELD(qn('%s_%s_seq' % (model._meta.db_table, f.column))),
     134                        style.SQL_FIELD(sequence_name),
    118135                        style.SQL_FIELD(qn(f.column)),
    119136                        style.SQL_FIELD(qn(f.column)),
    120137                        style.SQL_KEYWORD('IS NOT'),
    121138                        style.SQL_KEYWORD('FROM'),
    122                         style.SQL_TABLE(qn(model._meta.db_table))))
     139                        style.SQL_TABLE(model._meta.qualified_name)))
    123140                    break # Only one AutoField is allowed per model, so don't bother continuing.
    124141            for f in model._meta.many_to_many:
    125142                if not f.rel.through:
     143                    sequence_name = qn('%s_id_seq' % f.m2m_db_table()) # XXX: generic schemas support
     144                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     145                                                       sequence_name)
    126146                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    127147                        (style.SQL_KEYWORD('SELECT'),
    128                         style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
     148                        style.SQL_FIELD(sequence_name),
    129149                        style.SQL_FIELD(qn('id')),
    130150                        style.SQL_FIELD(qn('id')),
    131151                        style.SQL_KEYWORD('IS NOT'),
    132152                        style.SQL_KEYWORD('FROM'),
    133                         style.SQL_TABLE(qn(f.m2m_db_table()))))
     153                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
    134154        return output
    135155
    136156    def savepoint_create_sql(self, sid):
  • django/db/backends/sqlite3/base.py

    diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
    a b  
    9393                (style.SQL_KEYWORD('DELETE'),
    9494                 style.SQL_KEYWORD('FROM'),
    9595                 style.SQL_FIELD(self.quote_name(table))
    96                  ) for table in tables]
     96                 ) for (_, table) in tables]
    9797        # Note: No requirement for reset of auto-incremented indices (cf. other
    9898        # sql_flush() implementations). Just return SQL at this point
    9999        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  
    833833        if isinstance(self.rel.to, basestring):
    834834            target = self.rel.to
    835835        else:
    836             target = self.rel.to._meta.db_table
     836            target = self.rel.to._meta.qualified_name
    837837        cls._meta.duplicate_targets[self.column] = (target, "o2m")
    838838
    839839    def contribute_to_related_class(self, cls, related):
     
    922922        to = to.lower()
    923923    meta = type('Meta', (object,), {
    924924        'db_table': field._get_m2m_db_table(klass._meta),
     925        'db_schema': field._get_m2m_db_schema(klass._meta),
    925926        'managed': managed,
    926927        'auto_created': klass,
    927928        'app_label': klass._meta.app_label,
     
    951952            through=kwargs.pop('through', None))
    952953
    953954        self.db_table = kwargs.pop('db_table', None)
     955        self.db_schema = kwargs.pop('db_schema', '')
    954956        if kwargs['rel'].through is not None:
    955957            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    956958
     
    972974            return util.truncate_name('%s_%s' % (opts.db_table, self.name),
    973975                                      connection.ops.max_name_length())
    974976
     977    def _get_m2m_db_schema(self, opts):
     978        "Function that can be curried to provide the m2m schema name for this relation"
     979        if self.rel.through is not None and self.rel.through._meta.db_schema:
     980            return self.rel.through._meta.db_schema
     981        return self.db_schema
     982
     983    def _get_m2m_qualified_name(self, opts):
     984        "Function that can be curried to provide the qualified m2m table name for this relation"
     985        schema = self._get_m2m_db_schema(opts)
     986        table = self._get_m2m_db_table(opts)
     987        return connection.ops.prep_db_table(schema, table)
     988
    975989    def _get_m2m_attr(self, related, attr):
    976990        "Function that can be curried to provide the source column name for the m2m table"
    977991        cache_attr = '_m2m_%s_cache' % attr
     
    10611075
    10621076        # Set up the accessor for the m2m table name for the relation
    10631077        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
     1078        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
     1079        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
     1080                                        cls._meta)
    10641081
    10651082        # Populate some necessary rel arguments so that cross-app relations
    10661083        # work correctly.
     
    10721089        if isinstance(self.rel.to, basestring):
    10731090            target = self.rel.to
    10741091        else:
    1075             target = self.rel.to._meta.db_table
     1092            target = self.rel.to._meta.qualified_name
    10761093        cls._meta.duplicate_targets[self.column] = (target, "m2m")
    10771094
    10781095    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  
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract', 'managed', 'proxy', 'auto_created')
     24                 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    3030        self.module_name, self.verbose_name = None, None
    3131        self.verbose_name_plural = None
    3232        self.db_table = ''
     33        self.db_schema = settings.DATABASE_SCHEMA
     34        self.qualified_name = ''
    3335        self.ordering = []
    3436        self.unique_together =  []
    3537        self.permissions =  []
     
    104106            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    105107            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
    106108
     109        # Construct qualified table name.
     110        self.qualified_name = connection.ops.prep_db_table(self.db_schema,
     111                                                           self.db_table)
     112        if self.qualified_name == connection.ops.quote_name(self.db_table):
     113            # If unchanged, the backend doesn't support schemas.
     114            self.db_schema = ''
    107115
    108116    def _prepare(self, model):
    109117        if self.order_with_respect_to:
     
    177185        self.pk = target._meta.pk
    178186        self.proxy_for_model = target
    179187        self.db_table = target._meta.db_table
     188        self.db_schema = target._meta.db_schema
     189        self.qualified_name = target._meta.qualified_name
    180190
    181191    def __repr__(self):
    182192        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  
    2525        might not have all the pieces in place at that time.
    2626        """
    2727        if not self.query.tables:
    28             self.query.join((None, self.query.model._meta.db_table, None, None))
     28            self.query.join((None, self.query.model._meta.qualified_name, None, None))
    2929        if (not self.query.select and self.query.default_cols and not
    3030                self.query.included_inherited_models):
    3131            self.query.setup_inherited_models()
     
    255255                        alias = start_alias
    256256                    else:
    257257                        link_field = opts.get_ancestor_link(model)
    258                         alias = self.query.join((start_alias, model._meta.db_table,
     258                        alias = self.query.join((start_alias, model._meta.qualified_name,
    259259                                link_field.column, model._meta.pk.column))
    260260                    seen[model] = alias
    261261            else:
     
    456456                result.append('%s%s%s' % (connector, qn(name), alias_str))
    457457            first = False
    458458        for t in self.query.extra_tables:
    459             alias, unused = self.query.table_alias(t)
     459            alias, unused = self.query.table_alias(qn(t))
    460460            # Only add the alias if it's not already present (the table_alias()
    461461            # calls increments the refcount, so an alias refcount of one means
    462462            # this is the only reference.
     
    536536            # what "used" specifies).
    537537            avoid = avoid_set.copy()
    538538            dupe_set = orig_dupe_set.copy()
    539             table = f.rel.to._meta.db_table
     539            table = f.rel.to._meta.qualified_name
    540540            if nullable or f.null:
    541541                promote = True
    542542            else:
     
    560560                                ())
    561561                        dupe_set.add((opts, lhs_col))
    562562                    int_opts = int_model._meta
    563                     alias = self.query.join((alias, int_opts.db_table, lhs_col,
    564                             int_opts.pk.column), exclusions=used,
     563                    alias = self.query.join((alias, int_opts.qualified_name,
     564                            lhs_col, int_opts.pk.column), exclusions=used,
    565565                            promote=promote)
    566566                    alias_chain.append(alias)
    567567                    for (dupe_opts, dupe_col) in dupe_set:
     
    615615                # what "used" specifies).
    616616                avoid = avoid_set.copy()
    617617                dupe_set = orig_dupe_set.copy()
    618                 table = model._meta.db_table
     618                table = model._meta.qualified_name
    619619
    620620                int_opts = opts
    621621                alias = root_alias
     
    638638                            dupe_set.add((opts, lhs_col))
    639639                        int_opts = int_model._meta
    640640                        alias = self.query.join(
    641                             (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
     641                            (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column),
    642642                            exclusions=used, promote=True, reuse=used
    643643                        )
    644644                        alias_chain.append(alias)
     
    779779        # going to be column names (so we can avoid the extra overhead).
    780780        qn = self.connection.ops.quote_name
    781781        opts = self.query.model._meta
    782         result = ['INSERT INTO %s' % qn(opts.db_table)]
     782        result = ['INSERT INTO %s' % opts.qualified_name]
    783783        result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
    784784        values = [self.placeholder(*v) for v in self.query.values]
    785785        result.append('VALUES (%s)' % ', '.join(values))
    786786        params = self.query.params
    787787        if self.return_id and self.connection.features.can_return_id_from_insert:
    788             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
     788            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
    789789            r_fmt, r_params = self.connection.ops.return_insert_id()
    790790            result.append(r_fmt % col)
    791791            params = params + r_params
     
    799799        if self.connection.features.can_return_id_from_insert:
    800800            return self.connection.ops.fetch_returned_insert_id(cursor)
    801801        return self.connection.ops.last_insert_id(cursor,
    802                 self.query.model._meta.db_table, self.query.model._meta.pk.column)
     802                self.query.model._meta.db_schema, self.query.model._meta.db_table,
     803                self.query.model._meta.pk.column)
    803804
    804805
    805806class 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  
    570570        Callback used by deferred_to_columns(). The "target" parameter should
    571571        be a set instance.
    572572        """
    573         table = model._meta.db_table
     573        table = model._meta.qualified_name
    574574        if table not in target:
    575575            target[table] = set()
    576576        for field in fields:
    577577            target[table].add(field.column)
    578578
    579579
     580
    580581    def table_alias(self, table_name, create=False):
    581582        """
    582583        Returns a table alias for the given table_name and whether this is a
     
    752753            alias = self.tables[0]
    753754            self.ref_alias(alias)
    754755        else:
    755             alias = self.join((None, self.model._meta.db_table, None, None))
     756            alias = self.join((None, self.model._meta.qualified_name,
     757                               None, None))
    756758        return alias
    757759
    758760    def count_active_tables(self):
     
    865867                    seen[model] = root_alias
    866868                else:
    867869                    link_field = opts.get_ancestor_link(model)
    868                     seen[model] = self.join((root_alias, model._meta.db_table,
     870                    seen[model] = self.join((root_alias,
     871                            model._meta.qualified_name,
    869872                            link_field.column, model._meta.pk.column))
    870873        self.included_inherited_models = seen
    871874
     
    11901193                                    (id(opts), lhs_col), ()))
    11911194                            dupe_set.add((opts, lhs_col))
    11921195                        opts = int_model._meta
    1193                         alias = self.join((alias, opts.db_table, lhs_col,
    1194                                 opts.pk.column), exclusions=exclusions)
     1196                        alias = self.join((alias, opts.qualified_name,
     1197                                           lhs_col, opts.pk.column),
     1198                                          exclusions=exclusions)
    11951199                        joins.append(alias)
    11961200                        exclusions.add(alias)
    11971201                        for (dupe_opts, dupe_col) in dupe_set:
     
    12161220                        (table1, from_col1, to_col1, table2, from_col2,
    12171221                                to_col2, opts, target) = cached_data
    12181222                    else:
    1219                         table1 = field.m2m_db_table()
     1223                        table1 = field.m2m_qualified_name()
    12201224                        from_col1 = opts.pk.column
    12211225                        to_col1 = field.m2m_column_name()
    12221226                        opts = field.rel.to._meta
    1223                         table2 = opts.db_table
     1227                        table2 = opts.qualified_name
    12241228                        from_col2 = field.m2m_reverse_name()
    12251229                        to_col2 = opts.pk.column
    12261230                        target = opts.pk
     
    12471251                    else:
    12481252                        opts = field.rel.to._meta
    12491253                        target = field.rel.get_related_field()
    1250                         table = opts.db_table
     1254                        table = opts.qualified_name
    12511255                        from_col = field.column
    12521256                        to_col = target.column
    12531257                        orig_opts._join_cache[name] = (table, from_col, to_col,
     
    12691273                        (table1, from_col1, to_col1, table2, from_col2,
    12701274                                to_col2, opts, target) = cached_data
    12711275                    else:
    1272                         table1 = field.m2m_db_table()
     1276                        table1 = field.m2m_qualified_name()
    12731277                        from_col1 = opts.pk.column
    12741278                        to_col1 = field.m2m_reverse_name()
    12751279                        opts = orig_field.opts
    1276                         table2 = opts.db_table
     1280                        table2 = opts.qualified_name
    12771281                        from_col2 = field.m2m_column_name()
    12781282                        to_col2 = opts.pk.column
    12791283                        target = opts.pk
     
    12961300                        local_field = opts.get_field_by_name(
    12971301                                field.rel.field_name)[0]
    12981302                        opts = orig_field.opts
    1299                         table = opts.db_table
     1303                        table = opts.qualified_name
    13001304                        from_col = local_field.column
    13011305                        to_col = field.column
    13021306                        target = opts.pk
     
    15521556        else:
    15531557            opts = self.model._meta
    15541558            if not self.select:
    1555                 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
     1559                count = self.aggregates_module.Count((self.join((None,
     1560                           opts.qualified_name, None, None)), opts.pk.column),
    15561561                                         is_summary=True, distinct=True)
    15571562            else:
    15581563                # 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  
    4646                            'in',
    4747                            pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
    4848                            AND)
    49                     self.do_query(related.field.m2m_db_table(), where, using=using)
     49                    self.do_query(related.field.m2m_qualified_name(), where, using=using)
    5050
    5151        for f in cls._meta.many_to_many:
    5252            w1 = self.where_class()
     
    8181            field = self.model._meta.pk
    8282            where.add((Constraint(None, field.column, field), 'in',
    8383                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    84             self.do_query(self.model._meta.db_table, where, using=using)
     84            self.do_query(self.model._meta.qualified_name, where, using=using)
    8585
    8686class UpdateQuery(Query):
    8787    """
     
    197197        extras.update(kwargs)
    198198        return super(InsertQuery, self).clone(klass, **extras)
    199199
     200
    200201    def insert_values(self, insert_values, raw_values=False):
    201202        """
    202203        Set up the insert query from the 'insert_values' dictionary. The
  • 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/testschema.json

    diff --git a/tests/modeltests/schemas/fixtures/testschema.json b/tests/modeltests/schemas/fixtures/testschema.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 for m2m relations"
     37    name=models.CharField(max_length=20)
     38    entries=models.ManyToManyField(Entry)
     39
     40
     41
     42__test__ = {'API_TESTS': """
     43
     44# Test with actual data
     45# Nothing in there yet
     46>>> Blog.objects.all()
     47[]
     48
     49# Create a blog
     50>>> b = Blog(name='Test')
     51>>> b.save()
     52
     53# Verify that we got an ID
     54>>> b.id
     551
     56
     57# Create entry
     58>>> e = Entry(blog=b, title='Test entry')
     59>>> e.save()
     60>>> e.id
     611
     62
     63# Create Comments
     64>>> c1 = Comment(entry=e, text='nice entry')
     65>>> c1.save()
     66>>> c2 = Comment(entry=e, text='really like it')
     67>>> c2.save()
     68
     69# Retrieve the stuff again.
     70>>> b2 = Blog.objects.get(id=b.id)
     71>>> b==b2
     72True
     73
     74>>> b2.entry_set.all()
     75[<Entry: Entry object>]
     76
     77# Make sure we don't break many to many relations
     78>>> t = Tag(name="test")
     79>>> t.save()
     80>>> t.entries = [e]
     81>>> t.entries.all()
     82[<Entry: Entry object>]
     83
     84
     85>>> from django.conf import settings
     86>>> from django.db import connection, models
     87
     88# Test if we support schemas and can find the table if so
     89>>> if e._meta.db_schema:
     90...     tables = connection.introspection.schema_table_names(e._meta.db_schema)
     91... else:
     92...     tables = connection.introspection.table_names()
     93>>> if connection.introspection.table_name_converter(e._meta.db_table) in tables:
     94...     print "ok"
     95... else:
     96...     print "schema=" + e._meta.db_schema
     97...     print "tables=%s" % tables
     98ok
     99
     100
     101"""
     102}
  • 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
    - +  
     1# Validate that you can override the default test suite
     2
     3import unittest
     4from models import *
     5
     6from django.conf import settings
     7from django.db import connection, models, DEFAULT_DB_ALIAS
     8
     9class SampleTests(unittest.TestCase):
     10    fixtures = ['testschema',]
     11    VALID_ENGINES = ['postgresql_psycopg2', 'postgresql', 'mysql','oracle']
     12    VALID_ENGINES = ['django.db.backends.%s' %b for b in VALID_ENGINES]
     13
     14    def test_meta_information(self):
     15        e = Entry.objects.get(id=1)
     16        b = Blog.objects.get(id=1)
     17        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in self.VALID_ENGINES:
     18            self.assertEqual('test_schema', e._meta.db_schema)
     19            if settings.DATABASE_SCHEMA:
     20                self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema)
     21            else:
     22                self.assertFalse(b._meta.db_schema)
     23        else:
     24            self.assertFalse(e._meta.db_schema) # No model schema
     25            self.assertFalse(b._meta.db_schema) # No global schema
     26
     27    def test_schema_in_m2m_declaring_model(self):
     28        e = Entry.objects.get(id=1)
     29        c = Category(name='Test')
     30        c.save()
     31        e.categories = [c]
     32
     33        categories= e.categories.filter(name='Test')
     34
     35        a = len(categories)
     36        self.assertEqual(1, len(categories))
  • tests/regressiontests/introspection/tests.py

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