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

File 6148-generic-schema-support-r11951.diff, 73.9 KB (added by ramiro, 5 years ago)

Patch updated to r11951 (just before 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  
    130130DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
    131131DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
    132132DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
     133DATABASE_SCHEMA = ''           # Set to empty string for default.
    133134
    134135# The email backend to use. For possible shortcuts see django.core.mail.
    135136# The default is to use the SMTP backend.
  • django/conf/project_template/settings.py

    diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
    a b  
    1515DATABASE_PASSWORD = ''         # Not used with sqlite3.
    1616DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
    1717DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
     18DATABASE_SCHEMA = ''           # Set to empty string for default.
    1819
    1920# Local time zone for this installation. Choices can be found here:
    2021# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
  • django/contrib/contenttypes/generic.py

    diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
    a b  
    129129        return self.object_id_field_name
    130130
    131131    def m2m_reverse_name(self):
    132         return self.model._meta.pk.column
     132        return self.rel.to._meta.pk.column
     133
     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)
    133141
    134142    def contribute_to_class(self, cls, name):
    135143        super(GenericRelation, self).contribute_to_class(cls, name)
  • django/core/management/commands/syncdb.py

    diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
    a b  
    4949        cursor = connection.cursor()
    5050
    5151        # Get a list of already installed *models* so that references work right.
    52         tables = connection.introspection.table_names()
     52        tables = [('', tn) for tn in connection.introspection.table_names()]
    5353        seen_models = connection.introspection.installed_models(tables)
     54        seen_schemas = set()
    5455        created_models = set()
    5556        pending_references = {}
    5657
     
    5960            app_name = app.__name__.split('.')[-2]
    6061            model_list = models.get_models(app, include_auto_created=True)
    6162            for model in model_list:
    62                 # Create the model's database table, if it doesn't already exist.
     63                # Add model-defined schema tables if any.
     64                db_schema = model._meta.db_schema
     65                if db_schema:
     66                    db_schema = connection.introspection.schema_name_converter(db_schema)
     67                    if db_schema not in seen_schemas:
     68                        tables += [(db_schema, tn) for tn in
     69                                   connection.introspection.schema_table_names(db_schema)]
     70                        seen_schemas.add(db_schema)
     71
     72                # Create the model's database table,
     73                # if it doesn't already exist.
    6374                if verbosity >= 2:
    6475                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
    6576                opts = model._meta
    66                 if (connection.introspection.table_name_converter(opts.db_table) in tables or
    67                     (opts.auto_created and
    68                     connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)):
     77                schema_table = (db_schema, connection.introspection.table_name_converter(opts.db_table))
     78                if schema_table in tables or \
     79                    (opts.auto_created and \
     80                    (db_schema,
     81                     connection.introspection.table_name_converter(opts.auto_created._meta.db_table))
     82                    in tables):
    6983                    continue
    7084                sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
    7185                seen_models.add(model)
     
    7993                    print "Creating table %s" % model._meta.db_table
    8094                for statement in sql:
    8195                    cursor.execute(statement)
    82                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
     96                tables.append(schema_table)
    8397
    8498
    8599        transaction.commit_unless_managed()
  • django/core/management/sql.py

    diff --git a/django/core/management/sql.py b/django/core/management/sql.py
    a b  
    6868
    6969    # Figure out which tables already exist
    7070    if cursor:
    71         table_names = connection.introspection.get_table_list(cursor)
     71        table_names = [('', tn) for tn in
     72                       connection.introspection.get_table_list(cursor)]
    7273    else:
    7374        table_names = []
    7475
     
    7980
    8081    references_to_delete = {}
    8182    app_models = models.get_models(app, include_auto_created=True)
     83    seen_schemas = set()
    8284    for model in app_models:
    83         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     85        db_schema = model._meta.db_schema
     86        # Find additional tables in model-defined schemas.
     87        if db_schema:
     88            db_schema = connection.introspection.schema_name_converter(db_schema)
     89            if db_schema not in seen_schemas:
     90                table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)]
     91                seen_schemas.add(db_schema)
     92        schema_table = (db_schema,
     93                        connection.introspection.table_name_converter(model._meta.db_table))
     94
     95        if cursor and schema_table in table_names:
    8496            # The table exists, so it needs to be dropped
    8597            opts = model._meta
    8698            for f in opts.local_fields:
     
    90102            to_delete.add(model)
    91103
    92104    for model in app_models:
    93         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     105        db_schema = model._meta.db_schema
     106        if db_schema:
     107            db_schema = connection.introspection.schema_name_converter(db_schema)
     108        schema_table = (db_schema,
     109                        connection.introspection.table_name_converter(model._meta.db_table))
     110        if schema_table in table_names:
    94111            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
    95112
    96113    # Close database connection explicitly, in case this output is being piped
     
    116133    if only_django:
    117134        tables = connection.introspection.django_table_names(only_existing=True)
    118135    else:
    119         tables = connection.introspection.table_names()
     136        tables = [('', tn) for tn in connection.introspection.table_names()]
    120137    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
    121138    return statements
    122139
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    a b  
    109109    a backend performs ordering or calculates the ID of a recently-inserted
    110110    row.
    111111    """
    112     def autoinc_sql(self, table, column):
     112    def autoinc_sql(self, schema, table, column):
    113113        """
    114114        Returns any SQL needed to support auto-incrementing primary keys, or
    115115        None if no SQL is necessary.
     
    155155        """
    156156        return "DROP CONSTRAINT"
    157157
    158     def drop_sequence_sql(self, table):
     158    def drop_sequence_sql(self, schema, table):
    159159        """
    160160        Returns any SQL necessary to drop the sequence for the given table.
    161161        Returns None if no SQL is necessary.
     
    216216
    217217        return smart_unicode(sql) % u_params
    218218
    219     def last_insert_id(self, cursor, table_name, pk_name):
     219    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
    220220        """
    221221        Given a cursor object that has just performed an INSERT statement into
    222222        a table that has an auto-incrementing ID, returns the newly created ID.
     
    287287        """
    288288        raise NotImplementedError()
    289289
     290    def prep_db_table(self, db_schema, db_table):
     291        """
     292        Prepares and formats the table name if necessary.
     293        Just returns quoted db_table if not supported.
     294        """
     295        return self.quote_name(db_table)
     296
     297    def prep_db_index(self, db_schema, db_index):
     298        """
     299        Prepares and formats the table index name if necessary.
     300        Just returns quoted db_index if not supported.
     301        """
     302        return self.quote_name(db_index)
     303
    290304    def random_function_sql(self):
    291305        """
    292306        Returns a SQL expression that returns a random value.
     
    486500        return name
    487501
    488502    def table_names(self):
    489         "Returns a list of names of all tables that exist in the database."
     503        "Returns a list of names of all tables that exist in the default schema."
    490504        cursor = self.connection.cursor()
    491505        return self.get_table_list(cursor)
    492506
     507    def schema_name_converter(self, name):
     508        """Apply a conversion to the name for the purposes of comparison.
     509
     510        The default schema name converter is for case sensitive comparison.
     511        """
     512        return name
     513
     514    def get_schema_list(self, cursor):
     515        "Returns a list of schemas that exist in the database"
     516        return []
     517
     518    def get_schema_table_list(self, cursor, schema):
     519        "Returns a list of tables in a specific schema"
     520        return []
     521
     522    def schema_names(self):
     523        cursor = self.connection.cursor()
     524        return self.get_schema_list(cursor)
     525
     526    def schema_table_names(self, schema):
     527        "Returns a list of names of all tables that exist in the database schema."
     528        cursor = self.connection.cursor()
     529        return self.get_schema_table_list(cursor, schema)
     530
    493531    def django_table_names(self, only_existing=False):
    494532        """
    495         Returns a list of all table names that have associated Django models and
    496         are in INSTALLED_APPS.
     533        Returns a list of tuples containing all schema and table names that
     534        have associated Django models and are in INSTALLED_APPS.
    497535
    498         If only_existing is True, the resulting list will only include the tables
    499         that actually exist in the database.
     536        If only_existing is True, the resulting list will only include the
     537        tables that actually exist in the database.
    500538        """
    501539        from django.db import models
    502540        tables = set()
    503         for app in models.get_apps():
    504             for model in models.get_models(app):
    505                 if not model._meta.managed:
    506                     continue
    507                 tables.add(model._meta.db_table)
    508                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    509541        if only_existing:
    510             tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
     542            existing_tables = set([('', tn) for tn in self.table_names()])
     543            seen_schemas = set()
     544        for model in models.get_models():
     545            if not model._meta.managed:
     546                continue
     547            db_schema = model._meta.db_schema
     548            db_table = model._meta.db_table
     549            if only_existing and db_schema and db_schema not in seen_schemas:
     550                existing_tables.update([(db_schema, tn) for tn in
     551                                        self.schema_table_names(db_schema)])
     552                seen_schemas.add(db_schema)
     553            tables.add((model._meta.db_schema, model._meta.db_table))
     554            for f in model._meta.local_many_to_many:
     555                m2m_schema = f.m2m_db_schema()
     556                m2m_table = f.m2m_db_table()
     557                if only_existing and m2m_schema and m2m_schema not in seen_schemas:
     558                    existing_tables.update([(m2m_schema, tn) for tn in
     559                                        self.schema_table_names(m2m_schema)])
     560                    seen_schemas.add(m2m_schema)
     561                tables.add((m2m_schema, m2m_table))
     562        if only_existing:
     563            tables = [(s, t) for (s, t) in tables
     564                      if (self.schema_name_converter(s),
     565                          self.table_name_converter(t)) in existing_tables]
    511566        return tables
    512567
    513568    def installed_models(self, tables):
     
    534589                    continue
    535590                for f in model._meta.local_fields:
    536591                    if isinstance(f, models.AutoField):
    537                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
     592                        sequence_list.append({'table': model._meta.db_table,
     593                                              'column': f.column,
     594                                              'schema': model._meta.db_schema})
    538595                        break # Only one AutoField is allowed per model, so don't bother continuing.
    539596
    540597                for f in model._meta.local_many_to_many:
     598                    schema = f.m2m_db_schema()
    541599                    # If this is an m2m using an intermediate table,
    542600                    # we don't need to reset the sequence.
    543601                    if f.rel.through is None:
    544                         sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     602                        sequence_list.append({'table': f.m2m_db_table(),
     603                                              'column': None,
     604                                              'schema': schema})
    545605
    546606        return sequence_list
    547607
  • 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
     
    157172        from django.db.backends.util import truncate_name
    158173
    159174        output = []
    160         if f.creates_table:
     175        if f.rel.through._meta.auto_created:
    161176            opts = model._meta
    162177            qn = self.connection.ops.quote_name
    163178            tablespace = f.db_tablespace or opts.db_tablespace
     
    170185            else:
    171186                tablespace_sql = ''
    172187            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
    173                 style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     188                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
    174189            table_output.append('    %s %s %s%s,' %
    175190                (style.SQL_FIELD(qn('id')),
    176191                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
     
    202217                self.connection.ops.deferrable_sql()))
    203218
    204219            # Add any extra SQL needed to support auto-incrementing PKs
    205             autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
     220            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
     221                                                          f.m2m_db_table(),
     222                                                          'id')
    206223            if autoinc_sql:
    207224                for stmt in autoinc_sql:
    208225                    output.append(stmt)
     
    219236                (style.SQL_FIELD(qn(field.m2m_column_name())),
    220237                style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
    221238                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    222                 style.SQL_TABLE(qn(opts.db_table)),
     239                style.SQL_TABLE(opts.qualified_name),
    223240                style.SQL_FIELD(qn(opts.pk.column)),
    224241                self.connection.ops.deferrable_sql()),
    225242            '    %s %s %s %s (%s)%s,' %
    226243                (style.SQL_FIELD(qn(field.m2m_reverse_name())),
    227244                style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type()),
    228245                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    229                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
     246                style.SQL_TABLE(field.rel.to._meta.qualified_name),
    230247                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
    231248                self.connection.ops.deferrable_sql())
    232249        ]
     
    256273                    tablespace_sql = ''
    257274            else:
    258275                tablespace_sql = ''
     276            index_name = '%s_%s' % (model._meta.db_table, f.column)
     277            index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    259278            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    260                 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
     279                style.SQL_TABLE(index_name) + ' ' +
    261280                style.SQL_KEYWORD('ON') + ' ' +
    262                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
     281                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    263282                "(%s)" % style.SQL_FIELD(qn(f.column)) +
    264283                "%s;" % tablespace_sql]
    265284        else:
    266285            output = []
    267286        return output
    268287
     288    def sql_destroy_schema(self, schema, style):
     289        """"
     290        Returns the SQL required to destroy a single schema.
     291        """
     292        qn = self.connection.ops.quote_name
     293        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
     294        return output
     295
    269296    def sql_destroy_model(self, model, references_to_delete, style):
    270297        "Return the DROP TABLE and restraint dropping statements for a single model"
    271298        if not model._meta.managed or model._meta.proxy:
     
    273300        # Drop the table now
    274301        qn = self.connection.ops.quote_name
    275302        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
    276                               style.SQL_TABLE(qn(model._meta.db_table)))]
     303                              style.SQL_TABLE(model._meta.qualified_name))]
    277304        if model in references_to_delete:
    278305            output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
    279306
    280307        if model._meta.has_auto_field:
    281             ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
     308            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     309                                                       model._meta.db_table)
    282310            if ds:
    283311                output.append(ds)
    284312        return output
     
    292320        qn = self.connection.ops.quote_name
    293321        for rel_class, f in references_to_delete[model]:
    294322            table = rel_class._meta.db_table
     323            qname = rel_class._meta.qualified_name
    295324            col = f.column
    296325            r_table = model._meta.db_table
    297326            r_col = model._meta.get_field(f.rel.field_name).column
    298327            r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
    299328            output.append('%s %s %s %s;' % \
    300329                (style.SQL_KEYWORD('ALTER TABLE'),
    301                 style.SQL_TABLE(qn(table)),
     330                style.SQL_TABLE(qname),
    302331                style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
    303332                style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
    304333        del references_to_delete[model]
     
    308337        "Returns the DROP TABLE statements for a single m2m field"
    309338        qn = self.connection.ops.quote_name
    310339        output = []
    311         if f.creates_table:
     340        if f.rel.through._meta.auto_created:
    312341            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
    313                 style.SQL_TABLE(qn(f.m2m_db_table()))))
    314             ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
     342                style.SQL_TABLE(f.m2m_qualified_name())))
     343            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     344                               "%s_%s" % (model._meta.db_table, f.column))
    315345            if ds:
    316346                output.append(ds)
    317347        return output
     
    324354        if verbosity >= 1:
    325355            print "Creating test database..."
    326356
    327         test_database_name = self._create_test_db(verbosity, autoclobber)
     357        schema_apps = self._get_app_with_schemas()
     358        schemas = self._get_schemas(schema_apps)
     359        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
    328360
    329361        self.connection.close()
    330362        settings.DATABASE_NAME = test_database_name
     
    333365        settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
    334366        self.connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
    335367
     368        # Create the test schemas.
     369        cursor = self.connection.cursor()
     370        self._create_test_schemas(verbosity, schemas, cursor)
     371
    336372        call_command('syncdb', verbosity=verbosity, interactive=False)
    337373
    338374        if settings.CACHE_BACKEND.startswith('db://'):
     
    346382
    347383        return test_database_name
    348384
    349     def _create_test_db(self, verbosity, autoclobber):
     385    def _create_test_schemas(self, verbosity, schemas, cursor):
     386        from django.core.management.color import no_style
     387        style = no_style()
     388        for schema in schemas:
     389            if verbosity >= 1:
     390                print "Creating schema %s" % schema
     391            cursor.execute(self.sql_create_schema(schema, style))
     392
     393    def _destroy_test_schemas(self, verbosity, schemas, cursor):
     394        from django.core.management.color import no_style
     395        style = no_style()
     396        for schema in schemas:
     397            if verbosity >= 1:
     398                print "Destroying schema %s" % schema
     399            cursor.execute(self.sql_destroy_schema(schema, style))
     400            if verbosity >= 1:
     401                print "Schema %s destroyed" % schema
     402
     403    def _get_schemas(self, apps):
     404        from django.db import models
     405        schemas = set()
     406        for app in apps:
     407            app_models = models.get_models(app)
     408            for model in app_models:
     409                schema = model._meta.db_schema
     410                if not schema or schema in schemas:
     411                    continue
     412                schemas.add(schema)
     413        return schemas
     414
     415    def _get_app_with_schemas(self):
     416        from django.db import models
     417        apps = models.get_apps()
     418        schema_apps = set()
     419        for app in apps:
     420            app_models = models.get_models(app)
     421            for model in app_models:
     422                schema = model._meta.db_schema
     423                if not schema or app in schema_apps:
     424                    continue
     425                schema_apps.add(app)
     426        return schema_apps
     427
     428    def _create_test_db(self, verbosity, autoclobber, schemas):
    350429        "Internal implementation - creates the test db tables."
    351430        suffix = self.sql_table_creation_suffix()
    352431
     
    372451                try:
    373452                    if verbosity >= 1:
    374453                        print "Destroying old test database..."
     454                    self._destroy_test_schemas(verbosity, schemas, cursor)
    375455                    cursor.execute("DROP DATABASE %s" % qn(test_database_name))
    376456                    if verbosity >= 1:
    377457                        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  
    160160            return name # Quoting once is enough.
    161161        return "`%s`" % name
    162162
     163    def prep_db_table(self, db_schema, db_table):
     164        qn = self.quote_name
     165        if db_schema:
     166            return "%s.%s" % (qn(db_schema), qn(db_table))
     167        else:
     168            return qn(db_table)
     169
     170    def prep_db_index(self, db_schema, db_index):
     171        return self.prep_db_table(db_schema, db_index)
     172
    163173    def random_function_sql(self):
    164174        return 'RAND()'
    165175
     
    169179        # to clear all tables of all data
    170180        if tables:
    171181            sql = ['SET FOREIGN_KEY_CHECKS = 0;']
    172             for table in tables:
    173                 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
     182            for (schema, table) in tables:
     183                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
    174184            sql.append('SET FOREIGN_KEY_CHECKS = 1;')
    175185
    176186            # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
    177187            # to reset sequence indices
    178             sql.extend(["%s %s %s %s %s;" % \
    179                 (style.SQL_KEYWORD('ALTER'),
    180                  style.SQL_KEYWORD('TABLE'),
    181                  style.SQL_TABLE(self.quote_name(sequence['table'])),
    182                  style.SQL_KEYWORD('AUTO_INCREMENT'),
    183                  style.SQL_FIELD('= 1'),
    184                 ) for sequence in sequences])
     188            for sequence_info in sequences:
     189                schema_name = sequence_info['schema']
     190                table_name = self.prep_db_table(schema_name, sequence_info['table'])
     191                sql.append("%s %s %s %s %s;" % \
     192                           (style.SQL_KEYWORD('ALTER'),
     193                            style.SQL_KEYWORD('TABLE'),
     194                            style.SQL_TABLE(table_name),
     195                            style.SQL_KEYWORD('AUTO_INCREMENT'),
     196                            style.SQL_FIELD('= 1'),
     197                            ))
    185198            return sql
    186199        else:
    187200            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  
    4141    def sql_for_inline_foreign_key_references(self, field, known_models, style):
    4242        "All inline references are pending under MySQL"
    4343        return [], True
    44        
     44
    4545    def sql_for_inline_many_to_many_references(self, model, field, style):
    4646        from django.db import models
    4747        opts = model._meta
    4848        qn = self.connection.ops.quote_name
    49        
     49
    5050        table_output = [
    5151            '    %s %s %s,' %
    5252                (style.SQL_FIELD(qn(field.m2m_column_name())),
     
    6464                field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
    6565            ]
    6666        return table_output, deferred
    67        
    68  No newline at end of file
     67
     68    def default_schema(self):
     69        return settings.DATABASE_NAME
     70
     71    def sql_create_schema(self, schema, style):
     72        """
     73        Returns the SQL required to create a single schema.
     74        In MySQL schemas are synonymous to databases
     75        """
     76        qn = self.connection.ops.quote_name
     77        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
     78        return output
     79
     80    def sql_destroy_schema(self, schema, style):
     81        """"
     82        Returns the SQL required to destroy a single schema.
     83        """
     84        qn = self.connection.ops.quote_name
     85        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
     86        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  
    5555
    5656class DatabaseOperations(BaseDatabaseOperations):
    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()
     
    108110    def deferrable_sql(self):
    109111        return " DEFERRABLE INITIALLY DEFERRED"
    110112
    111     def drop_sequence_sql(self, table):
    112         return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
     113    def drop_sequence_sql(self, schema, table):
     114        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
     115        return "DROP SEQUENCE %s;" % sequence_name
    113116
    114117    def fetch_returned_insert_id(self, cursor):
    115118        return long(cursor._insert_id_var.getvalue())
     
    120123        else:
    121124            return "%s"
    122125
    123     def last_insert_id(self, cursor, table_name, pk_name):
    124         sq_name = get_sequence_name(table_name)
    125         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
     126    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     127        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
     128        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
    126129        return cursor.fetchone()[0]
    127130
    128131    def lookup_cast(self, lookup_type):
     
    133136    def max_name_length(self):
    134137        return 30
    135138
     139    def prep_db_table(self, db_schema, db_table):
     140        qn = self.quote_name
     141        if db_schema:
     142            return "%s.%s" % (qn(db_schema), qn(db_table))
     143        else:
     144            return qn(db_table)
     145
     146    def prep_db_index(self, db_schema, db_index):
     147        return self.prep_db_table(db_schema, db_index)
     148
    136149    def prep_for_iexact_query(self, x):
    137150        return x
    138151
     
    186199    def sql_flush(self, style, tables, sequences):
    187200        # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
    188201        # 'TRUNCATE z;'... style SQL statements
     202        sql = []
    189203        if tables:
    190204            # Oracle does support TRUNCATE, but it seems to get us into
    191205            # FK referential trouble, whereas DELETE FROM table works.
    192             sql = ['%s %s %s;' % \
    193                     (style.SQL_KEYWORD('DELETE'),
    194                      style.SQL_KEYWORD('FROM'),
    195                      style.SQL_FIELD(self.quote_name(table)))
    196                     for table in tables]
     206            for schema, table in tables:
     207                table = self.prep_db_table(schema, table)
     208                sql.append('%s %s %s;' % \
     209                           (style.SQL_KEYWORD('DELETE'),
     210                            style.SQL_KEYWORD('FROM'),
     211                            style.SQL_FIELD(table)))
    197212            # Since we've just deleted all the rows, running our sequence
    198213            # ALTER code will reset the sequence to 0.
    199214            for sequence_info in sequences:
    200                 sequence_name = get_sequence_name(sequence_info['table'])
    201                 table_name = self.quote_name(sequence_info['table'])
     215                schema_name = sequence_info['schema']
     216                sequence_name = self.prep_db_table(schema_name,
     217                                    get_sequence_name(sequence_info['table']))
     218                table_name = self.prep_db_table(schema_name,
     219                                                sequence_info['table'])
    202220                column_name = self.quote_name(sequence_info['column'] or 'id')
    203221                query = _get_sequence_reset_sql() % {'sequence': sequence_name,
    204222                                                     'table': table_name,
    205223                                                     'column': column_name}
    206224                sql.append(query)
    207             return sql
    208         else:
    209             return []
     225        return sql
    210226
    211227    def sequence_reset_sql(self, style, model_list):
    212228        from django.db import models
     
    215231        for model in model_list:
    216232            for f in model._meta.local_fields:
    217233                if isinstance(f, models.AutoField):
    218                     table_name = self.quote_name(model._meta.db_table)
    219                     sequence_name = get_sequence_name(model._meta.db_table)
     234                    table_name = model._meta.qualified_name
     235                    sequence_name = self.prep_db_table(model._meta.db_schema,
     236                                       get_sequence_name(model._meta.db_table))
    220237                    column_name = self.quote_name(f.column)
    221238                    output.append(query % {'sequence': sequence_name,
    222239                                           'table': table_name,
     
    226243                    break
    227244            for f in model._meta.many_to_many:
    228245                if not f.rel.through:
    229                     table_name = self.quote_name(f.m2m_db_table())
    230                     sequence_name = get_sequence_name(f.m2m_db_table())
     246                    table_name = self.quote_name(f.m2m_qualified_name())
     247                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     248                                           get_sequence_name(f.m2m_db_table()))
    231249                    column_name = self.quote_name('id')
    232250                    output.append(query % {'sequence': sequence_name,
    233251                                           'table': table_name,
     
    551569BEGIN
    552570    LOCK TABLE %(table)s IN SHARE MODE;
    553571    SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
    554     SELECT "%(sequence)s".nextval INTO cval FROM dual;
     572    SELECT %(sequence)s.nextval INTO cval FROM dual;
    555573    cval := startvalue - cval;
    556574    IF cval != 0 THEN
    557         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
    558         SELECT "%(sequence)s".nextval INTO cval FROM dual;
    559         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
     575        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
     576        SELECT %(sequence)s.nextval INTO cval FROM dual;
     577        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
    560578    END IF;
    561579    COMMIT;
    562580END;
  • django/db/backends/oracle/creation.py

    diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
    a b  
    4040        'URLField':                     'VARCHAR2(%(max_length)s)',
    4141    }
    4242
     43    def sql_create_schema(self, schema, style, password=None,
     44                          tablespace=None, temp_tablespace=None):
     45        qn = self.connection.ops.quote_name
     46        lock_account = (password is None)
     47        if lock_account:
     48            password = schema
     49        output = []
     50        output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'),
     51                                       qn(schema),
     52                                       style.SQL_KEYWORD('IDENTIFIED BY'),
     53                                       qn(password)))
     54        if tablespace:
     55            output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'),
     56                                     qn(tablespace)))
     57        if temp_tablespace:
     58            output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'),
     59                                     qn(temp_tablespace)))
     60        if lock_account:
     61            output.append(style.SQL_KEYWORD('ACCOUNT LOCK'))
     62        return '\n'.join(output)
     63
     64    def sql_destroy_schema(self, schema, style):
     65        qn = self.connection.ops.quote_name
     66        return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema),
     67                             style.SQL_KEYWORD('CASCADE'))
     68
    4369    remember = {}
    4470
    45     def _create_test_db(self, verbosity=1, autoclobber=False):
     71    def _create_test_db(self, verbosity=1, autoclobber=False, schema):
    4672        TEST_DATABASE_NAME = self._test_database_name(settings)
    4773        TEST_DATABASE_USER = self._test_database_user(settings)
    4874        TEST_DATABASE_PASSWD = self._test_database_passwd(settings)
     
    175201               DEFAULT TABLESPACE %(tblspace)s
    176202               TEMPORARY TABLESPACE %(tblspace_temp)s
    177203            """,
    178             """GRANT CONNECT, RESOURCE TO %(user)s""",
     204            """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""",
    179205        ]
    180206        self._execute_statements(cursor, statements, parameters, verbosity)
    181207
  • 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  
    5151                tablespace_sql = ''
    5252
    5353            def get_index_sql(index_name, opclass=''):
     54                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    5455                return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    55                         style.SQL_TABLE(qn(index_name)) + ' ' +
     56                        style.SQL_TABLE(index_name) + ' ' +
    5657                        style.SQL_KEYWORD('ON') + ' ' +
    57                         style.SQL_TABLE(qn(db_table)) + ' ' +
     58                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    5859                        "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
    5960                        "%s;" % tablespace_sql)
    6061
  • 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  
    5252            return 'HOST(%s)'
    5353        return '%s'
    5454
    55     def last_insert_id(self, cursor, table_name, pk_name):
    56         cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
     55    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     56        sequence_name = '%s_%s_seq' % (table_name, pk_name)
     57        sequence_name = self.prep_db_table(schema_name, sequence_name)
     58        cursor.execute("SELECT CURRVAL('%s')" % sequence_name)
    5759        return cursor.fetchone()[0]
    5860
    5961    def no_limit_value(self):
     
    6466            return name # Quoting once is enough.
    6567        return '"%s"' % name
    6668
     69    def prep_db_table(self, db_schema, db_table):
     70        qn = self.quote_name
     71        if db_schema:
     72            return "%s.%s" % (qn(db_schema), qn(db_table))
     73        else:
     74            return qn(db_table)
     75
    6776    def sql_flush(self, style, tables, sequences):
    6877        if tables:
     78            qnames = [self.prep_db_table(schema, table)
     79                      for (schema, table) in tables]
    6980            if self.postgres_version[0:2] >= (8,1):
    7081                # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
    7182                # in order to be able to truncate tables referenced by a foreign
     
    7384                # statement.
    7485                sql = ['%s %s;' % \
    7586                    (style.SQL_KEYWORD('TRUNCATE'),
    76                      style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
     87                     style.SQL_FIELD(', '.join(qnames))
    7788                )]
    7889            else:
    7990                # Older versions of Postgres can't do TRUNCATE in a single call, so
     
    8192                sql = ['%s %s %s;' % \
    8293                        (style.SQL_KEYWORD('DELETE'),
    8394                         style.SQL_KEYWORD('FROM'),
    84                          style.SQL_FIELD(self.quote_name(table))
    85                          ) for table in tables]
     95                         style.SQL_FIELD(qname)
     96                         ) for qname in qnames]
    8697
    8798            # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
    8899            # to reset sequence indices
    89100            for sequence_info in sequences:
     101                schema_name = sequence_info['schema']
    90102                table_name = sequence_info['table']
    91103                column_name = sequence_info['column']
    92104                if column_name and len(column_name) > 0:
    93105                    sequence_name = '%s_%s_seq' % (table_name, column_name)
    94106                else:
    95107                    sequence_name = '%s_id_seq' % table_name
     108                sequence_name = self.prep_db_table(schema_name, sequence_name)
    96109                sql.append("%s setval('%s', 1, false);" % \
    97110                    (style.SQL_KEYWORD('SELECT'),
    98                     style.SQL_FIELD(self.quote_name(sequence_name)))
     111                    style.SQL_FIELD(sequence_name))
    99112                )
    100113            return sql
    101114        else:
     
    111124            # if there are records (as the max pk value is already in use), otherwise set it to false.
    112125            for f in model._meta.local_fields:
    113126                if isinstance(f, models.AutoField):
     127                    sequence_name = qn('%s_%s_seq' % (model._meta.db_table,
     128                                                      f.column)) # XXX: generic schemas support
     129                    sequence_name = self.prep_db_table(model._meta.db_schema,
     130                                                       sequence_name)
    114131                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    115132                        (style.SQL_KEYWORD('SELECT'),
    116                         style.SQL_FIELD(qn('%s_%s_seq' % (model._meta.db_table, f.column))),
     133                        style.SQL_FIELD(sequence_name),
    117134                        style.SQL_FIELD(qn(f.column)),
    118135                        style.SQL_FIELD(qn(f.column)),
    119136                        style.SQL_KEYWORD('IS NOT'),
    120137                        style.SQL_KEYWORD('FROM'),
    121                         style.SQL_TABLE(qn(model._meta.db_table))))
     138                        style.SQL_TABLE(model._meta.qualified_name)))
    122139                    break # Only one AutoField is allowed per model, so don't bother continuing.
    123140            for f in model._meta.many_to_many:
    124141                if not f.rel.through:
     142                    sequence_name = qn('%s_id_seq' % f.m2m_db_table()) # XXX: generic schemas support
     143                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     144                                                       sequence_name)
    125145                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    126146                        (style.SQL_KEYWORD('SELECT'),
    127                         style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
     147                        style.SQL_FIELD(sequence_name),
    128148                        style.SQL_FIELD(qn('id')),
    129149                        style.SQL_FIELD(qn('id')),
    130150                        style.SQL_KEYWORD('IS NOT'),
    131151                        style.SQL_KEYWORD('FROM'),
    132                         style.SQL_TABLE(qn(f.m2m_db_table()))))
     152                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
    133153        return output
    134154
    135155    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  
    9494                (style.SQL_KEYWORD('DELETE'),
    9595                 style.SQL_KEYWORD('FROM'),
    9696                 style.SQL_FIELD(self.quote_name(table))
    97                  ) for table in tables]
     97                 ) for (_, table) in tables]
    9898        # Note: No requirement for reset of auto-incremented indices (cf. other
    9999        # sql_flush() implementations). Just return SQL at this point
    100100        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  
    3030        'TextField':                    'text',
    3131        'TimeField':                    'time',
    3232    }
    33    
     33
    3434    def sql_for_pending_references(self, model, style, pending_references):
    3535        "SQLite3 doesn't support constraints"
    3636        return []
     
    3838    def sql_remove_table_constraints(self, model, references_to_delete, style):
    3939        "SQLite3 doesn't support constraints"
    4040        return []
    41        
    42     def _create_test_db(self, verbosity, autoclobber):
     41
     42    def _create_test_db(self, verbosity, autoclobber, schemas):
    4343        if settings.TEST_DATABASE_NAME and settings.TEST_DATABASE_NAME != ":memory:":
    4444            test_database_name = settings.TEST_DATABASE_NAME
    4545            # Erase the old test database
     
    6464        else:
    6565            test_database_name = ":memory:"
    6666        return test_database_name
    67        
     67
    6868    def _destroy_test_db(self, test_database_name, verbosity):
    6969        if test_database_name and test_database_name != ":memory:":
    7070            # Remove the SQLite database file
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    a b  
    611611            # into a pure queryset operation.
    612612            where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
    613613                (qn('_order'), op, qn('_order'),
    614                 qn(self._meta.db_table), qn(self._meta.pk.column))]
     614                self._meta.qualified_name, qn(self._meta.pk.column))]
    615615            params = [self.pk]
    616616            obj = self._default_manager.filter(**{order_field.name: getattr(self, order_field.attname)}).extra(where=where, params=params).order_by(order)[:1].get()
    617617            setattr(self, cachename, obj)
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    752752        if isinstance(self.rel.to, basestring):
    753753            target = self.rel.to
    754754        else:
    755             target = self.rel.to._meta.db_table
     755            target = self.rel.to._meta.qualified_name
    756756        cls._meta.duplicate_targets[self.column] = (target, "o2m")
    757757
    758758    def contribute_to_related_class(self, cls, related):
     
    835835        to = to.lower()
    836836    meta = type('Meta', (object,), {
    837837        'db_table': field._get_m2m_db_table(klass._meta),
     838        'db_schema': field._get_m2m_db_schema(klass._meta),
    838839        'managed': managed,
    839840        'auto_created': klass,
    840841        'app_label': klass._meta.app_label,
     
    864865            through=kwargs.pop('through', None))
    865866
    866867        self.db_table = kwargs.pop('db_table', None)
     868        self.db_schema = kwargs.pop('db_schema', '')
    867869        if kwargs['rel'].through is not None:
    868870            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    869871
     
    885887            return util.truncate_name('%s_%s' % (opts.db_table, self.name),
    886888                                      connection.ops.max_name_length())
    887889
     890    def _get_m2m_db_schema(self, opts):
     891        "Function that can be curried to provide the m2m schema name for this relation"
     892        if self.rel.through is not None and self.rel.through._meta.db_schema:
     893            return self.rel.through._meta.db_schema
     894        return self.db_schema
     895
     896    def _get_m2m_qualified_name(self, opts):
     897        "Function that can be curried to provide the qualified m2m table name for this relation"
     898        schema = self._get_m2m_db_schema(opts)
     899        table = self._get_m2m_db_table(opts)
     900        return connection.ops.prep_db_table(schema, table)
     901
    888902    def _get_m2m_attr(self, related, attr):
    889903        "Function that can be curried to provide the source column name for the m2m table"
    890904        cache_attr = '_m2m_%s_cache' % attr
     
    974988
    975989        # Set up the accessor for the m2m table name for the relation
    976990        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
     991        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
     992        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
     993                                        cls._meta)
    977994
    978995        # Populate some necessary rel arguments so that cross-app relations
    979996        # work correctly.
     
    9851002        if isinstance(self.rel.to, basestring):
    9861003            target = self.rel.to
    9871004        else:
    988             target = self.rel.to._meta.db_table
     1005            target = self.rel.to._meta.qualified_name
    9891006        cls._meta.duplicate_targets[self.column] = (target, "m2m")
    9901007
    9911008    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/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    a b  
    620620        might not have all the pieces in place at that time.
    621621        """
    622622        if not self.tables:
    623             self.join((None, self.model._meta.db_table, None, None))
     623            self.join((None, self.model._meta.qualified_name, None, None))
    624624        if (not self.select and self.default_cols and not
    625625                self.included_inherited_models):
    626626            self.setup_inherited_models()
     
    720720        Callback used by deferred_to_columns(). The "target" parameter should
    721721        be a set instance.
    722722        """
    723         table = model._meta.db_table
     723        table = model._meta.qualified_name
    724724        if table not in target:
    725725            target[table] = set()
    726726        for field in fields:
     
    837837                        alias = start_alias
    838838                    else:
    839839                        link_field = opts.get_ancestor_link(model)
    840                         alias = self.join((start_alias, model._meta.db_table,
     840                        alias = self.join((start_alias,
     841                                model._meta.qualified_name,
    841842                                link_field.column, model._meta.pk.column))
    842843                    seen[model] = alias
    843844            else:
     
    900901                result.append('%s%s%s' % (connector, qn(name), alias_str))
    901902            first = False
    902903        for t in self.extra_tables:
    903             alias, unused = self.table_alias(t)
     904            alias, unused = self.table_alias(qn(t))
    904905            # Only add the alias if it's not already present (the table_alias()
    905906            # calls increments the refcount, so an alias refcount of one means
    906907            # this is the only reference.
     
    12451246            alias = self.tables[0]
    12461247            self.ref_alias(alias)
    12471248        else:
    1248             alias = self.join((None, self.model._meta.db_table, None, None))
     1249            alias = self.join((None, self.model._meta.qualified_name,
     1250                               None, None))
    12491251        return alias
    12501252
    12511253    def count_active_tables(self):
     
    13581360                    seen[model] = root_alias
    13591361                else:
    13601362                    link_field = opts.get_ancestor_link(model)
    1361                     seen[model] = self.join((root_alias, model._meta.db_table,
     1363                    seen[model] = self.join((root_alias,
     1364                            model._meta.qualified_name,
    13621365                            link_field.column, model._meta.pk.column))
    13631366        self.included_inherited_models = seen
    13641367
     
    14161419            # what "used" specifies).
    14171420            avoid = avoid_set.copy()
    14181421            dupe_set = orig_dupe_set.copy()
    1419             table = f.rel.to._meta.db_table
     1422            table = f.rel.to._meta.qualified_name
    14201423            if nullable or f.null:
    14211424                promote = True
    14221425            else:
     
    14401443                                ())
    14411444                        dupe_set.add((opts, lhs_col))
    14421445                    int_opts = int_model._meta
    1443                     alias = self.join((alias, int_opts.db_table, lhs_col,
    1444                             int_opts.pk.column), exclusions=used,
     1446                    alias = self.join((alias, int_opts.qualified_name,
     1447                            lhs_col, int_opts.pk.column), exclusions=used,
    14451448                            promote=promote)
    14461449                    alias_chain.append(alias)
    14471450                    for (dupe_opts, dupe_col) in dupe_set:
     
    17951798                                    (id(opts), lhs_col), ()))
    17961799                            dupe_set.add((opts, lhs_col))
    17971800                        opts = int_model._meta
    1798                         alias = self.join((alias, opts.db_table, lhs_col,
    1799                                 opts.pk.column), exclusions=exclusions)
     1801                        alias = self.join((alias, opts.qualified_name,
     1802                                           lhs_col, opts.pk.column),
     1803                                          exclusions=exclusions)
    18001804                        joins.append(alias)
    18011805                        exclusions.add(alias)
    18021806                        for (dupe_opts, dupe_col) in dupe_set:
     
    18211825                        (table1, from_col1, to_col1, table2, from_col2,
    18221826                                to_col2, opts, target) = cached_data
    18231827                    else:
    1824                         table1 = field.m2m_db_table()
     1828                        table1 = field.m2m_qualified_name()
    18251829                        from_col1 = opts.pk.column
    18261830                        to_col1 = field.m2m_column_name()
    18271831                        opts = field.rel.to._meta
    1828                         table2 = opts.db_table
     1832                        table2 = opts.qualified_name
    18291833                        from_col2 = field.m2m_reverse_name()
    18301834                        to_col2 = opts.pk.column
    18311835                        target = opts.pk
     
    18521856                    else:
    18531857                        opts = field.rel.to._meta
    18541858                        target = field.rel.get_related_field()
    1855                         table = opts.db_table
     1859                        table = opts.qualified_name
    18561860                        from_col = field.column
    18571861                        to_col = target.column
    18581862                        orig_opts._join_cache[name] = (table, from_col, to_col,
     
    18741878                        (table1, from_col1, to_col1, table2, from_col2,
    18751879                                to_col2, opts, target) = cached_data
    18761880                    else:
    1877                         table1 = field.m2m_db_table()
     1881                        table1 = field.m2m_qualified_name()
    18781882                        from_col1 = opts.pk.column
    18791883                        to_col1 = field.m2m_reverse_name()
    18801884                        opts = orig_field.opts
    1881                         table2 = opts.db_table
     1885                        table2 = opts.qualified_name
    18821886                        from_col2 = field.m2m_column_name()
    18831887                        to_col2 = opts.pk.column
    18841888                        target = opts.pk
     
    19011905                        local_field = opts.get_field_by_name(
    19021906                                field.rel.field_name)[0]
    19031907                        opts = orig_field.opts
    1904                         table = opts.db_table
     1908                        table = opts.qualified_name
    19051909                        from_col = local_field.column
    19061910                        to_col = field.column
    19071911                        target = opts.pk
     
    21402144        self.group_by = []
    21412145        if self.connection.features.allows_group_by_pk:
    21422146            if len(self.select) == len(self.model._meta.fields):
    2143                 self.group_by.append((self.model._meta.db_table,
     2147                self.group_by.append((self.model._meta.qualified_name,
    21442148                                      self.model._meta.pk.column))
    21452149                return
    21462150
     
    21622166        else:
    21632167            opts = self.model._meta
    21642168            if not self.select:
    2165                 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
     2169                count = self.aggregates_module.Count((self.join((None,
     2170                           opts.qualified_name, None, None)), opts.pk.column),
    21662171                                         is_summary=True, distinct=True)
    21672172            else:
    21682173                # 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  
    5454                            'in',
    5555                            pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
    5656                            AND)
    57                     self.do_query(related.field.m2m_db_table(), where)
     57                    self.do_query(related.field.m2m_qualified_name(), where)
    5858
    5959        for f in cls._meta.many_to_many:
    6060            w1 = self.where_class()
     
    8585            field = self.model._meta.pk
    8686            where.add((Constraint(None, field.column, field), 'in',
    8787                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    88             self.do_query(self.model._meta.db_table, where)
     88            self.do_query(self.model._meta.qualified_name, where)
    8989
    9090class UpdateQuery(Query):
    9191    """
     
    304304        # going to be column names (so we can avoid the extra overhead).
    305305        qn = self.connection.ops.quote_name
    306306        opts = self.model._meta
    307         result = ['INSERT INTO %s' % qn(opts.db_table)]
     307        result = ['INSERT INTO %s' % opts.qualified_name]
    308308        result.append('(%s)' % ', '.join([qn(c) for c in self.columns]))
    309309        result.append('VALUES (%s)' % ', '.join(self.values))
    310310        params = self.params
    311311        if self.return_id and self.connection.features.can_return_id_from_insert:
    312             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
     312            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
    313313            r_fmt, r_params = self.connection.ops.return_insert_id()
    314314            result.append(r_fmt % col)
    315315            params = params + r_params
     
    323323        if self.connection.features.can_return_id_from_insert:
    324324            return self.connection.ops.fetch_returned_insert_id(cursor)
    325325        return self.connection.ops.last_insert_id(cursor,
    326                 self.model._meta.db_table, self.model._meta.pk.column)
     326                self.model._meta.db_schema, self.model._meta.db_table,
     327                self.model._meta.pk.column)
    327328
    328329    def insert_values(self, insert_values, raw_values=False):
    329330        """
  • docs/ref/models/options.txt

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

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    a b  
    272272The port to use when connecting to the database. An empty string means the
    273273default port. Not used with SQLite.
    274274
     275.. setting:: DATABASE_SCHEMA
     276
     277DATABASE_SCHEMA
     278---------------
     279
     280.. versionadded:: 1.3
     281
     282Default: ``''`` (Empty string)
     283
     284The name of the database schema to use for models. If the backend
     285doesn't support multiple schemas, this option is ignored. An empty
     286string means the default schema.
     287
     288If this is used then Django will prefix any table names with the schema name.
     289The schema can be overriden on a per-model basis, for details see
     290:ref:`db_schema`.
     291
    275292.. setting:: DATABASE_USER
    276293
    277294DATABASE_USER
  • 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
     8
     9class SampleTests(unittest.TestCase):
     10    fixtures = ['testschema',]
     11    VALID_ENGINES=['postgresql_psycopg2', 'postgresql', 'mysql','oracle']
     12
     13    def test_meta_information(self):
     14        e=Entry.objects.get(id=1)
     15        b = Blog.objects.get(id=1)
     16        if settings.DATABASE_ENGINE in self.VALID_ENGINES:
     17            self.assertEqual('test_schema', e._meta.db_schema)
     18            if settings.DATABASE_SCHEMA:
     19                self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema)
     20            else:
     21                self.assertFalse(b._meta.db_schema)
     22        else:
     23            self.assertFalse(e._meta.db_schema) #no model schema
     24            self.assertFalse(b._meta.db_schema) #no global schema
     25
     26    def test_schema_in_m2m_declaring_model(self):
     27        e = Entry.objects.get(id=1)
     28        c = Category(name='Test')
     29        c.save()
     30        e.categories = [c]
     31
     32        categories= e.categories.filter(name='Test')
     33
     34        a = len(categories)
     35        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