Ticket #6148: generic-db_schema-r8463.diff

File generic-db_schema-r8463.diff, 20.1 KB (added by crippledcanary, 7 years ago)

Still some issues when testing, not the test themself

  • django/core/management/commands/syncdb.py

     
    6161            app_name = app.__name__.split('.')[-2]
    6262            model_list = models.get_models(app)
    6363            for model in model_list:
     64                # Add model defined schema tables if anny
     65                if model._meta.db_schema:
     66                    tables += connection.introspection.schema_table_names(model._meta.db_schema)
    6467                # Create the model's database table, if it doesn't already exist.
    6568                if verbosity >= 2:
    6669                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
  • django/core/management/sql.py

     
    8484    references_to_delete = {}
    8585    app_models = models.get_models(app)
    8686    for model in app_models:
     87        schema = model._meta.db_schema
     88        # Find aditional tables in model defined schemas
     89        if schema:
     90            table_names += connection.introspection.get_schema_table_list(cursor, schema)
    8791        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
    8892            # The table exists, so it needs to be dropped
    8993            opts = model._meta
  • django/db/backends/__init__.py

     
    225225        """
    226226        raise NotImplementedError()
    227227
     228    def prep_db_table(self, db_schema, db_table):
     229        """
     230        Prepares and formats the table name if neccesary.
     231        Just returns the db_table if not supported
     232        """
     233        return db_table
     234
    228235    def random_function_sql(self):
    229236        """
    230237        Returns a SQL expression that returns a random value.
     
    382389        cursor = self.connection.cursor()
    383390        return self.get_table_list(cursor)
    384391
     392    def schema_name_converter(self, name):
     393        """Apply a conversion to the name for the purposes of comparison.
     394
     395        The default schema name converter is for case sensitive comparison.
     396        """
     397        return name
     398
     399    def get_schema_list(self, cursor):
     400        "Returns a list of schemas that exist in the database"
     401        return []
     402   
     403    def get_schema_table_list(self, cursor, schema):
     404        "Returns a list of tables in a specific schema"
     405        return []
     406       
     407    def schema_names(self):
     408        cursor = self.connection.cursor()
     409        return self.get_schema_list(cursor)
     410   
     411    def schema_table_names(self, schema):
     412        "Returns a list of names of all tables that exist in the database schema."
     413        cursor = self.connection.cursor()
     414        return self.get_schema_table_list(cursor, schema)
     415
    385416    def django_table_names(self, only_existing=False):
    386417        """
    387418        Returns a list of all table names that have associated Django models and
  • django/db/backends/creation.py

     
    2525    def __init__(self, connection):
    2626        self.connection = connection
    2727
     28    def default_schema(self):
     29        return ""
     30
     31    def sql_create_schema(self, schema, style):
     32        """"
     33        Returns the SQL required to create a single schema
     34        """
     35        qn = self.connection.ops.quote_name
     36        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
     37        return output
     38
    2839    def sql_create_model(self, model, style, known_models=set()):
    2940        """
    3041        Returns the SQL required to create a single model, as a tuple of:
     
    122133                r_col = f.column
    123134                table = opts.db_table
    124135                col = opts.get_field(f.rel.field_name).column
     136                # Add schema if we are related to a model in different schema
     137                # and we are not in a different schema ourselfs
     138                if rel_opts.db_schema and not opts.db_schema:
     139                    table =  "%s.%s" % (self.default_schema(), table)
    125140                # For MySQL, r_name must be unique in the first 64 characters.
    126141                # So we are careful with character usage here.
    127142                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
     
    243258                    tablespace_sql = ''
    244259            else:
    245260                tablespace_sql = ''
     261            # Use original db_table in index name if schema is provided
     262            if model._meta.db_schema:
     263                index_table_name = model._meta._db_table
     264            else:
     265                index_table_name = model._meta.db_table
    246266            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    247                 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
     267                style.SQL_TABLE(qn('%s_%s' % (index_table_name, f.column))) + ' ' +
    248268                style.SQL_KEYWORD('ON') + ' ' +
    249269                style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
    250270                "(%s)" % style.SQL_FIELD(qn(f.column)) +
     
    253273            output = []
    254274        return output
    255275
     276    def sql_destroy_schema(self, schema, style):
     277        """"
     278        Returns the SQL required to create a single schema
     279        """
     280        qn = self.connection.ops.quote_name
     281        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
     282        return output
     283
    256284    def sql_destroy_model(self, model, references_to_delete, style):
    257285        "Return the DROP TABLE and restraint dropping statements for a single model"
    258286        # Drop the table now
     
    324352
    325353        return test_database_name
    326354
     355    def _create_test_schemas(self, verbosity, schemas, cursor):
     356        from django.core.management.color import color_style
     357        style = color_style()
     358        for schema in schemas:
     359            if verbosity >= 1:
     360                print "Creating schema %s" % schema
     361            cursor.execute(self.sql_create_schema(schema, style))
     362
     363    def _destroy_test_schemas(self, verbosity, schemas, cursor):
     364        from django.core.management.color import color_style
     365        style = color_style()
     366        for schema in schemas:
     367            if verbosity >= 1:
     368                print "Destroying schema %s" % schema
     369                cursor.execute(self.sql_destroy_schema(schema, style))
     370            if verbosity >= 1:
     371                print "Schema %s destroyed" % schema
     372
     373    def _get_schemas(self, apps):
     374        from django.db import models
     375        schemas = set()
     376        for app in apps:
     377            app_models = models.get_models(app)
     378            for model in app_models:
     379                schema = model._meta.db_schema
     380                if not schema or schema in schemas:
     381                    continue
     382                schemas.add(schema)
     383        return schemas
     384
     385    def _get_app_with_schemas(self):
     386        from django.db import models
     387        apps = models.get_apps()
     388        schema_apps = set()
     389        for app in apps:
     390            app_models = models.get_models(app)
     391            for model in app_models:
     392                schema = model._meta.db_schema
     393                if not schema or app in schema_apps:
     394                    continue
     395                schema_apps.add(app)
     396                continue
     397               
     398        return schema_apps
     399
    327400    def _create_test_db(self, verbosity, autoclobber):
    328401        "Internal implementation - creates the test db tables."
     402        schema_apps = self._get_app_with_schemas()
     403        schemas = self._get_schemas(schema_apps)
    329404        suffix = self.sql_table_creation_suffix()
    330405
    331406        if settings.TEST_DATABASE_NAME:
     
    342417        self.set_autocommit()
    343418        try:
    344419            cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
     420            #Connect to the new database to create schemas in it
     421            self.connection.close()
     422            settings.DATABASE_NAME = test_database_name
     423            cursor = self.connection.cursor()
     424            self.set_autocommit()
     425            self._create_test_schemas(verbosity, schemas, cursor)
    345426        except Exception, e:
    346427            sys.stderr.write("Got an error creating the test database: %s\n" % e)
    347428            if not autoclobber:
     
    350431                try:
    351432                    if verbosity >= 1:
    352433                        print "Destroying old test database..."
     434                    self._destroy_test_schemas(verbosity, schemas, cursor)
    353435                    cursor.execute("DROP DATABASE %s" % qn(test_database_name))
    354436                    if verbosity >= 1:
    355437                        print "Creating test database..."
    356438                    cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
     439                    #Connect to the new database to create schemas in it
     440                    self.connection.close()
     441                    settings.DATABASE_NAME = test_database_name
     442                    cursor = self.connection.cursor()
     443                    self.set_autocommit()
     444                    self._create_test_schemas(verbosity, schemas, cursor)
    357445                except Exception, e:
    358446                    sys.stderr.write("Got an error recreating the test database: %s\n" % e)
    359447                    sys.exit(2)
  • django/db/backends/mysql/base.py

     
    100100    def quote_name(self, name):
    101101        if name.startswith("`") and name.endswith("`"):
    102102            return name # Quoting once is enough.
    103         return "`%s`" % name
     103        # add support for tablenames passed that also have their schema in their name
     104        return "`%s`" % name.replace('.','`.`')
    104105
     106    def prep_db_table(self, db_schema, db_table):
     107        return "%s.%s" % (db_schema, db_table)
     108
    105109    def random_function_sql(self):
    106110        return 'RAND()'
    107111
  • django/db/backends/mysql/creation.py

     
    6565                field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
    6666            ]
    6767        return table_output, deferred
    68        
    69  No newline at end of file
     68
     69    def default_schema(self):
     70        return settings.DATABASE_NAME
     71
     72    def sql_create_schema(self, schema, style):
     73        """
     74        Returns the SQL required to create a single schema.
     75        In MySQL schemas are synonymous to databases
     76        """
     77        qn = self.connection.ops.quote_name
     78        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
     79        return output
     80
     81    def sql_destroy_schema(self, schema, style):
     82        """"
     83        Returns the SQL required to create a single schema
     84        """
     85        qn = self.connection.ops.quote_name
     86        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
     87        return output
     88 No newline at end of file
  • django/db/backends/mysql/introspection.py

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

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

     
    4747        return '%s'
    4848
    4949    def last_insert_id(self, cursor, table_name, pk_name):
    50         cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
     50        # add support for tablenames passed that also have their schema in their name
     51        cursor.execute(("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)).replace('.', '"."'))
    5152        return cursor.fetchone()[0]
    5253
    5354    def no_limit_value(self):
     
    5657    def quote_name(self, name):
    5758        if name.startswith('"') and name.endswith('"'):
    5859            return name # Quoting once is enough.
    59         return '"%s"' % name
     60        # add support for tablenames passed that also have their schema in their name
     61        return '"%s"' % name.replace('.','"."')
    6062
     63    def prep_db_table(self, db_schema, db_table):
     64        return "%s.%s" % (db_schema, db_table)
     65
    6166    def sql_flush(self, style, tables, sequences):
    6267        if tables:
    6368            if self.postgres_version[0] >= 8 and self.postgres_version[1] >= 1:
  • django/db/models/options.py

     
    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')
     24                 'abstract', 'db_schema')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    2929        self.module_name, self.verbose_name = None, None
    3030        self.verbose_name_plural = None
    3131        self.db_table = ''
     32        self.db_schema = ''
    3233        self.ordering = []
    3334        self.unique_together =  []
    3435        self.permissions =  []
     
    9596            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    9697            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
    9798
     99        # Patch db_table with the schema if provided and allowed
     100        if self.db_schema:
     101            # Store original db_table in a save place first
     102            self._db_table = self.db_table
     103            self.db_table = connection.ops.prep_db_table(self.db_schema, self.db_table)
     104            # If no changes were done then backend don't support schemas
     105            if self._db_table == self.db_table:
     106                self.db_schema = ''
    98107
    99108    def _prepare(self, model):
    100109        if self.order_with_respect_to:
  • docs/model-api.txt

     
    12101210that aren't allowed in Python variable names -- notably, the hyphen --
    12111211that's OK. Django quotes column and table names behind the scenes.
    12121212
     1213``db_schema``
     1214-----------------
     1215
     1216**New in Django development version**
     1217
     1218The name of the database schema to use for the model. If the backend
     1219doesn't support multiple schemas, this options is ignored.
     1220
     1221If this is used Django will prefix any table names with the schema name.
     1222For MySQL Django would use ``db_schema + '.' + db_table``.
     1223
     1224
    12131225``db_tablespace``
    12141226-----------------
    12151227
  • tests/modeltests/schemas/__init__.py

     
     1#
  • tests/modeltests/schemas/models.py

     
     1# coding: utf-8
     2
     3from django.db import models
     4
     5
     6class Blog(models.Model):
     7    "Model in default schema"
     8    name = models.CharField(max_length=50)
     9
     10   
     11class Entry(models.Model):
     12    "Model in custom schema that reference the default"
     13    blog = models.ForeignKey(Blog)   
     14    title = models.CharField(max_length=50)
     15   
     16    class Meta:
     17        "using custom db_table as well"
     18        db_table='schema_blog_entries'
     19        db_schema = 'test_schema'
     20       
     21
     22class Comment(models.Model):
     23    "Model in the custom schema that references Entry in the same schema"
     24    entry = models.ForeignKey(Entry)
     25    text = models.CharField(max_length=50)
     26   
     27    class Meta:
     28        db_schema = 'test_schema'
     29
     30__test__ = {'API_TESTS': """
     31
     32#Test with actual data
     33# Nothing in there yet
     34>>> Blog.objects.all()
     35[]
     36
     37# Create a blog
     38>>> b = Blog(name='Test')
     39>>> b.save()
     40
     41# Verify that we got an ID
     42>>> b.id
     431
     44
     45# Create entry
     46>>> e = Entry(blog=b, title='Test entry')
     47>>> e.save()
     48>>> e.id
     491
     50
     51# Create Comments
     52>>> c1 = Comment(entry=e, text='nice entry')
     53>>> c1.save()
     54>>> c2 = Comment(entry=e, text='really like it')
     55>>> c2.save()
     56
     57#Retrieve the stuff again.
     58>>> b2 = Blog.objects.get(id=b.id)
     59>>> b==b2
     60True
     61
     62>>> b2.entry_set.all()
     63[<Entry: Entry object>]
     64
     65>>> from django.conf import settings
     66>>> from django.db import connection, models
     67
     68# Test if we support schemas and can find the table if so
     69>>> if e._meta.db_schema:
     70...     tables = connection.introspection.schema_table_names(e._meta.db_schema)
     71... else:
     72...     tables = connection.introspection.table_names()
     73>>> if connection.introspection.table_name_converter(e._meta.db_table) in tables:
     74...     print "ok"
     75... else:
     76...     print "schema=" + e._meta.db_schema
     77...     print tables
     78ok
     79
     80# Test that all but sqlite3 backend suports schema and doesn't drop it.
     81# Oracle is not tested
     82>>> if settings.DATABASE_ENGINE != 'sqlite3' and settings.DATABASE_ENGINE != 'oracle':
     83...     if e._meta.db_schema != 'test_schema':
     84...         print "shouldn't drop or modify schema"
     85
     86>>> from django.core.management.sql import *
     87>>> from django.core.management.color import color_style
     88
     89>>> style = color_style()
     90>>> app = models.get_app('schemas')
     91
     92# Get the sql_create sequence
     93>>> a = sql_create(app, style)
     94
     95
     96#Done
     97"""
     98}
Back to Top