Ticket #6148: 6148-r13366.diff
File 6148-r13366.diff, 77.6 KB (added by , 14 years ago) |
---|
-
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
a b 145 145 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 146 146 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 147 147 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 148 DATABASE_SCHEMA = '' # Set to empty string for default. 148 149 149 150 # New format 150 151 DATABASES = { -
django/conf/project_template/settings.py
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
a b 17 17 'PASSWORD': '', # Not used with sqlite3. 18 18 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 19 19 'PORT': '', # Set to empty string for default. Not used with sqlite3. 20 'SCHEMA': '', # Set to empty string for default. 20 21 } 21 22 } 22 23 -
django/contrib/contenttypes/generic.py
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
a b 131 131 def m2m_reverse_name(self): 132 132 return self.rel.to._meta.pk.column 133 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) 141 134 142 def contribute_to_class(self, cls, name): 135 143 super(GenericRelation, self).contribute_to_class(cls, name) 136 144 -
django/core/management/commands/syncdb.py
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
a b 52 52 cursor = connection.cursor() 53 53 54 54 # Get a list of already installed *models* so that references work right. 55 tables = connection.introspection.table_names() 55 schemas = connection.introspection.schema_names() 56 if schemas: 57 tables = [] 58 default_schema_name = connection.features.default_schema_name 59 for schema in connection.introspection.schema_names(): 60 if default_schema_name and schema == default_schema_name: 61 sn = '' 62 else: 63 sn = schema 64 for tn in connection.introspection.schema_table_names(schema): 65 tables.append((sn, tn)) 66 else: 67 tables = [('', tn) for tn in connection.introspection.table_names()] 56 68 seen_models = connection.introspection.installed_models(tables) 69 seen_schemas = set() 57 70 created_models = set() 58 71 pending_references = {} 59 72 … … 64 77 if router.allow_syncdb(db, m)]) 65 78 for app in models.get_apps() 66 79 ] 80 81 def model_schema(model): 82 db_schema = model._meta.db_schema 83 if db_schema: 84 db_schema = connection.introspection.table_name_converter(db_schema) 85 return db_schema 86 67 87 def model_installed(model): 68 88 opts = model._meta 69 89 converter = connection.introspection.table_name_converter 70 return not ((converter(opts.db_table) in tables) or 71 (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables)) 90 db_schema = model_schema(model) 91 schema_table = (db_schema, converter(opts.db_table)) 92 return not ((schema_table in tables) or 93 (opts.auto_created and \ 94 (db_schema, converter(opts.auto_created._meta.db_table)) in tables) 95 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables) 96 ) 72 97 73 98 manifest = SortedDict( 74 99 (app_name, filter(model_installed, model_list)) … … 78 103 # Create the tables for each model 79 104 for app_name, model_list in manifest.items(): 80 105 for model in model_list: 106 # Add model-defined schema tables if any. 107 db_schema = model_schema(model) 108 if db_schema and db_schema not in seen_schemas: 109 tables += [(db_schema, tn) for tn in 110 connection.introspection.schema_table_names(db_schema)] 111 seen_schemas.add(db_schema) 112 81 113 # Create the model's database table, if it doesn't already exist. 82 114 if verbosity >= 2: 83 115 print "Processing %s.%s model" % (app_name, model._meta.object_name) … … 90 122 sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references)) 91 123 sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references)) 92 124 if verbosity >= 1 and sql: 93 print "Creating table %s" % model._meta.db_table 125 if db_schema: 126 print "Creating table %s.%s" % (db_schema, model._meta.db_table) 127 else: 128 print "Creating table %s" % model._meta.db_table 94 129 for statement in sql: 95 130 cursor.execute(statement) 96 tables.append(connection.introspection.table_name_converter(model._meta.db_table)) 131 if sql: 132 tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table))) 97 133 98 134 99 135 transaction.commit_unless_managed(using=db) -
django/core/management/sql.py
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
a b 66 66 67 67 # Figure out which tables already exist 68 68 if cursor: 69 table_names = connection.introspection.get_table_list(cursor) 69 table_names = [('', tn) for tn in 70 connection.introspection.get_table_list(cursor)] 70 71 else: 71 72 table_names = [] 72 73 … … 77 78 78 79 references_to_delete = {} 79 80 app_models = models.get_models(app, include_auto_created=True) 81 seen_schemas = set() 80 82 for model in app_models: 81 if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names: 83 db_schema = model._meta.db_schema 84 # Find additional tables in model-defined schemas. 85 if db_schema: 86 db_schema = connection.introspection.schema_name_converter(db_schema) 87 if db_schema not in seen_schemas: 88 table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)] 89 seen_schemas.add(db_schema) 90 schema_table = (db_schema, 91 connection.introspection.table_name_converter(model._meta.db_table)) 92 93 if cursor and schema_table in table_names: 82 94 # The table exists, so it needs to be dropped 83 95 opts = model._meta 84 96 for f in opts.local_fields: … … 88 100 to_delete.add(model) 89 101 90 102 for model in app_models: 91 if connection.introspection.table_name_converter(model._meta.db_table) in table_names: 103 db_schema = model._meta.db_schema 104 if db_schema: 105 db_schema = connection.introspection.schema_name_converter(db_schema) 106 schema_table = (db_schema, 107 connection.introspection.table_name_converter(model._meta.db_table)) 108 if schema_table in table_names: 92 109 output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style)) 93 110 94 111 # Close database connection explicitly, in case this output is being piped … … 113 130 if only_django: 114 131 tables = connection.introspection.django_table_names(only_existing=True) 115 132 else: 116 tables = connection.introspection.table_names()133 tables = [('', tn) for tn in connection.introspection.table_names()] 117 134 statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list()) 118 135 return statements 119 136 -
django/db/__init__.py
diff --git a/django/db/__init__.py b/django/db/__init__.py
a b 29 29 'TEST_CHARSET': settings.TEST_DATABASE_CHARSET, 30 30 'TEST_COLLATION': settings.TEST_DATABASE_COLLATION, 31 31 'TEST_NAME': settings.TEST_DATABASE_NAME, 32 'SCHEMA': settings.DATABASE_SCHEMA, 32 33 } 33 34 34 35 if DEFAULT_DB_ALIAS not in settings.DATABASES: -
django/db/backends/__init__.py
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
a b 96 96 # integer primary keys. 97 97 related_fields_match_type = False 98 98 allow_sliced_subqueries = True 99 default_schema_name = '' 99 100 100 101 class BaseDatabaseOperations(object): 101 102 """ … … 108 109 def __init__(self): 109 110 self._cache = {} 110 111 111 def autoinc_sql(self, table, column):112 def autoinc_sql(self, schema, table, column): 112 113 """ 113 114 Returns any SQL needed to support auto-incrementing primary keys, or 114 115 None if no SQL is necessary. … … 154 155 """ 155 156 return "DROP CONSTRAINT" 156 157 157 def drop_sequence_sql(self, table):158 def drop_sequence_sql(self, schema, table): 158 159 """ 159 160 Returns any SQL necessary to drop the sequence for the given table. 160 161 Returns None if no SQL is necessary. … … 215 216 216 217 return smart_unicode(sql) % u_params 217 218 218 def last_insert_id(self, cursor, table_name, pk_name):219 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 219 220 """ 220 221 Given a cursor object that has just performed an INSERT statement into 221 222 a table that has an auto-incrementing ID, returns the newly created ID. … … 289 290 """ 290 291 raise NotImplementedError() 291 292 293 def prep_db_table(self, db_schema, db_table): 294 """ 295 Prepares and formats the table name if necessary. 296 Just returns quoted db_table if not supported. 297 """ 298 return self.quote_name(db_table) 299 300 def prep_db_index(self, db_schema, db_index): 301 """ 302 Prepares and formats the table index name if necessary. 303 Just returns quoted db_index if not supported. 304 """ 305 return self.quote_name(db_index) 306 292 307 def random_function_sql(self): 293 308 """ 294 309 Returns a SQL expression that returns a random value. … … 493 508 return name 494 509 495 510 def table_names(self): 496 "Returns a list of names of all tables that exist in the d atabase."511 "Returns a list of names of all tables that exist in the default schema." 497 512 cursor = self.connection.cursor() 498 513 return self.get_table_list(cursor) 499 514 515 def schema_name_converter(self, name): 516 """Apply a conversion to the name for the purposes of comparison. 517 518 The default schema name converter is for case sensitive comparison. 519 """ 520 return name 521 522 def get_schema_list(self, cursor): 523 "Returns a list of schemas that exist in the database" 524 return [] 525 526 def get_schema_table_list(self, cursor, schema): 527 "Returns a list of tables in a specific schema" 528 return [] 529 530 def schema_names(self): 531 cursor = self.connection.cursor() 532 return self.get_schema_list(cursor) 533 534 def schema_table_names(self, schema): 535 "Returns a list of names of all tables that exist in the database schema." 536 cursor = self.connection.cursor() 537 return self.get_schema_table_list(cursor, schema) 538 500 539 def django_table_names(self, only_existing=False): 501 540 """ 502 Returns a list of all table names that have associated Django models and503 are in INSTALLED_APPS.541 Returns a list of tuples containing all schema and table names that 542 have associated Django models and are in INSTALLED_APPS. 504 543 505 If only_existing is True, the resulting list will only include the tables506 t hat actually exist in the database.544 If only_existing is True, the resulting list will only include the 545 tables that actually exist in the database. 507 546 """ 508 547 from django.db import models, router 509 548 tables = set() 510 for app in models.get_apps():511 for model in models.get_models(app):512 if not model._meta.managed:513 continue514 if not router.allow_syncdb(self.connection.alias, model):515 continue516 tables.add(model._meta.db_table)517 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])518 549 if only_existing: 519 tables = [t for t in tables if self.table_name_converter(t) in self.table_names()] 550 existing_tables = set([('', tn) for tn in self.table_names()]) 551 seen_schemas = set() 552 for model in models.get_models(): 553 if not model._meta.managed: 554 continue 555 if not router.allow_syncdb(self.connection.alias, model): 556 continue 557 db_schema = model._meta.db_schema 558 if only_existing and db_schema and db_schema not in seen_schemas: 559 existing_tables.update([(db_schema, tn) for tn in 560 self.schema_table_names(db_schema)]) 561 seen_schemas.add(db_schema) 562 tables.add((db_schema, model._meta.db_table)) 563 m2m_tables = [] 564 for f in model._meta.local_many_to_many: 565 m2m_schema = f.m2m_db_schema() 566 m2m_table = f.m2m_db_table() 567 if only_existing and m2m_schema and m2m_schema not in seen_schemas: 568 existing_tables.update([(m2m_schema, tn) for tn in 569 self.schema_table_names(m2m_schema)]) 570 seen_schemas.add(m2m_schema) 571 m2m_tables.append((m2m_schema, m2m_table)) 572 tables.update(m2m_tables) 573 if only_existing: 574 tables = [(s, t) for (s, t) in tables 575 if (self.schema_name_converter(s), 576 self.table_name_converter(t)) in existing_tables] 520 577 return tables 521 578 522 579 def installed_models(self, tables): … … 546 603 continue 547 604 for f in model._meta.local_fields: 548 605 if isinstance(f, models.AutoField): 549 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 606 sequence_list.append({'table': model._meta.db_table, 607 'column': f.column, 608 'schema': model._meta.db_schema}) 550 609 break # Only one AutoField is allowed per model, so don't bother continuing. 551 610 552 611 for f in model._meta.local_many_to_many: 612 schema = f.m2m_db_schema() 553 613 # If this is an m2m using an intermediate table, 554 614 # we don't need to reset the sequence. 555 615 if f.rel.through is None: 556 sequence_list.append({'table': f.m2m_db_table(), 'column': None}) 616 sequence_list.append({'table': f.m2m_db_table(), 617 'column': None, 618 'schema': schema}) 557 619 558 620 return sequence_list 559 621 -
django/db/backends/creation.py
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
a b 27 27 """ 28 28 return '%x' % (abs(hash(args)) % 4294967296L) # 2**32 29 29 30 def default_schema(self): 31 return "" 32 33 def sql_create_schema(self, schema, style): 34 """" 35 Returns the SQL required to create a single schema 36 """ 37 qn = self.connection.ops.quote_name 38 output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema)) 39 return output 40 30 41 def sql_create_model(self, model, style, known_models=set()): 31 42 """ 32 43 Returns the SQL required to create a single model, as a tuple of: … … 72 83 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ 73 84 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])) 74 85 75 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE( qn(opts.db_table)) + ' (']86 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' ('] 76 87 for i, line in enumerate(table_output): # Combine and add commas. 77 88 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 78 89 full_statement.append(')') … … 84 95 if opts.has_auto_field: 85 96 # Add any extra SQL needed to support auto-incrementing primary keys. 86 97 auto_column = opts.auto_field.db_column or opts.auto_field.name 87 autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column) 98 autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema, 99 opts.db_table, 100 auto_column) 88 101 if autoinc_sql: 89 102 for stmt in autoinc_sql: 90 103 final_output.append(stmt) … … 96 109 qn = self.connection.ops.quote_name 97 110 if field.rel.to in known_models: 98 111 output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \ 99 style.SQL_TABLE( qn(field.rel.to._meta.db_table)) + ' (' + \112 style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \ 100 113 style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' + 101 114 self.connection.ops.deferrable_sql() 102 115 ] … … 122 135 for rel_class, f in pending_references[model]: 123 136 rel_opts = rel_class._meta 124 137 r_table = rel_opts.db_table 138 r_qname = rel_opts.qualified_name 125 139 r_col = f.column 126 140 table = opts.db_table 141 qname = opts.qualified_name 127 142 col = opts.get_field(f.rel.field_name).column 128 143 # For MySQL, r_name must be unique in the first 64 characters. 129 144 # So we are careful with character usage here. 130 145 r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table)) 131 146 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ 132 ( qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),133 qn(r_col), qn (table), qn(col),147 (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())), 148 qn(r_col), qname, qn(col), 134 149 self.connection.ops.deferrable_sql())) 135 150 del pending_references[model] 136 151 return final_output … … 161 176 from django.db.backends.util import truncate_name 162 177 163 178 output = [] 164 if f. auto_created:179 if f.rel.through._meta.auto_created: 165 180 opts = model._meta 166 181 qn = self.connection.ops.quote_name 167 182 tablespace = f.db_tablespace or opts.db_tablespace … … 174 189 else: 175 190 tablespace_sql = '' 176 191 table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ 177 style.SQL_TABLE(qn(f.m2m_ db_table())) + ' (']192 style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' ('] 178 193 table_output.append(' %s %s %s%s,' % 179 194 (style.SQL_FIELD(qn('id')), 180 195 style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)), … … 206 221 self.connection.ops.deferrable_sql())) 207 222 208 223 # Add any extra SQL needed to support auto-incrementing PKs 209 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id') 224 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(), 225 f.m2m_db_table(), 226 'id') 210 227 if autoinc_sql: 211 228 for stmt in autoinc_sql: 212 229 output.append(stmt) … … 229 246 (style.SQL_FIELD(qn(field.m2m_column_name())), 230 247 style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)), 231 248 style.SQL_KEYWORD('NOT NULL REFERENCES'), 232 style.SQL_TABLE( qn(opts.db_table)),249 style.SQL_TABLE(opts.qualified_name), 233 250 style.SQL_FIELD(qn(opts.pk.column)), 234 251 self.connection.ops.deferrable_sql()), 235 252 ' %s %s %s %s (%s)%s,' % 236 253 (style.SQL_FIELD(qn(field.m2m_reverse_name())), 237 254 style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)), 238 255 style.SQL_KEYWORD('NOT NULL REFERENCES'), 239 style.SQL_TABLE( qn(field.rel.to._meta.db_table)),256 style.SQL_TABLE(field.rel.to._meta.qualified_name), 240 257 style.SQL_FIELD(qn(field.rel.to._meta.pk.column)), 241 258 self.connection.ops.deferrable_sql()) 242 259 ] … … 269 286 else: 270 287 tablespace_sql = '' 271 288 i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column)) 289 i_name = self.connection.ops.prep_db_index(model._meta.db_schema, i_name) 272 290 output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' + 273 291 style.SQL_TABLE(qn(truncate_name(i_name, self.connection.ops.max_name_length()))) + ' ' + 274 292 style.SQL_KEYWORD('ON') + ' ' + 275 style.SQL_TABLE( qn(model._meta.db_table)) + ' ' +293 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 276 294 "(%s)" % style.SQL_FIELD(qn(f.column)) + 277 295 "%s;" % tablespace_sql] 278 296 else: 279 297 output = [] 280 298 return output 281 299 300 def sql_destroy_schema(self, schema, style): 301 """" 302 Returns the SQL required to destroy a single schema. 303 """ 304 qn = self.connection.ops.quote_name 305 output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema)) 306 return output 307 282 308 def sql_destroy_model(self, model, references_to_delete, style): 283 309 "Return the DROP TABLE and restraint dropping statements for a single model" 284 310 if not model._meta.managed or model._meta.proxy: … … 286 312 # Drop the table now 287 313 qn = self.connection.ops.quote_name 288 314 output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 289 style.SQL_TABLE( qn(model._meta.db_table)))]315 style.SQL_TABLE(model._meta.qualified_name))] 290 316 if model in references_to_delete: 291 317 output.extend(self.sql_remove_table_constraints(model, references_to_delete, style)) 292 318 293 319 if model._meta.has_auto_field: 294 ds = self.connection.ops.drop_sequence_sql(model._meta.db_table) 320 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 321 model._meta.db_table) 295 322 if ds: 296 323 output.append(ds) 297 324 return output … … 305 332 qn = self.connection.ops.quote_name 306 333 for rel_class, f in references_to_delete[model]: 307 334 table = rel_class._meta.db_table 335 qname = rel_class._meta.qualified_name 308 336 col = f.column 309 337 r_table = model._meta.db_table 310 338 r_col = model._meta.get_field(f.rel.field_name).column 311 339 r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table)) 312 340 output.append('%s %s %s %s;' % \ 313 341 (style.SQL_KEYWORD('ALTER TABLE'), 314 style.SQL_TABLE(qn (table)),342 style.SQL_TABLE(qname), 315 343 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()), 316 344 style.SQL_FIELD(qn(truncate_name(r_name, self.connection.ops.max_name_length()))))) 317 345 del references_to_delete[model] … … 327 355 328 356 qn = self.connection.ops.quote_name 329 357 output = [] 330 if f. auto_created:358 if f.rel.through._meta.auto_created: 331 359 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), 332 style.SQL_TABLE(qn(f.m2m_db_table())))) 333 ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column)) 360 style.SQL_TABLE(f.m2m_qualified_name()))) 361 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 362 "%s_%s" % (model._meta.db_table, f.column)) 334 363 if ds: 335 364 output.append(ds) 336 365 return output … … 343 372 if verbosity >= 1: 344 373 print "Creating test database '%s'..." % self.connection.alias 345 374 346 test_database_name = self._create_test_db(verbosity, autoclobber) 375 schema_apps = self._get_app_with_schemas() 376 schemas = self._get_schemas(schema_apps) 377 test_database_name = self._create_test_db(verbosity, autoclobber, schemas) 347 378 348 379 self.connection.close() 349 380 self.connection.settings_dict["NAME"] = test_database_name 350 381 can_rollback = self._rollback_works() 351 382 self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback 352 383 384 # Create the test schemas. 385 cursor = self.connection.cursor() 386 self._create_test_schemas(verbosity, schemas, cursor) 387 353 388 call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) 354 389 355 390 if settings.CACHE_BACKEND.startswith('db://'): … … 363 398 364 399 return test_database_name 365 400 366 def _create_test_db(self, verbosity, autoclobber): 401 def _create_test_schemas(self, verbosity, schemas, cursor): 402 from django.core.management.color import no_style 403 style = no_style() 404 for schema in schemas: 405 if verbosity >= 1: 406 print "Creating schema %s" % schema 407 cursor.execute(self.sql_create_schema(schema, style)) 408 409 def _destroy_test_schemas(self, verbosity, schemas, cursor): 410 from django.core.management.color import no_style 411 style = no_style() 412 for schema in schemas: 413 if verbosity >= 1: 414 print "Destroying schema %s" % schema 415 cursor.execute(self.sql_destroy_schema(schema, style)) 416 if verbosity >= 1: 417 print "Schema %s destroyed" % schema 418 419 def _get_schemas(self, apps): 420 from django.db import models 421 schemas = set() 422 for app in apps: 423 app_models = models.get_models(app) 424 for model in app_models: 425 schema = model._meta.db_schema 426 if not schema or schema in schemas: 427 continue 428 schemas.add(schema) 429 return schemas 430 431 def _get_app_with_schemas(self): 432 from django.db import models 433 apps = models.get_apps() 434 schema_apps = set() 435 for app in apps: 436 app_models = models.get_models(app) 437 for model in app_models: 438 schema = model._meta.db_schema 439 if not schema or app in schema_apps: 440 continue 441 schema_apps.add(app) 442 return schema_apps 443 444 def _create_test_db(self, verbosity, autoclobber, schemas): 367 445 "Internal implementation - creates the test db tables." 368 446 suffix = self.sql_table_creation_suffix() 369 447 … … 389 467 try: 390 468 if verbosity >= 1: 391 469 print "Destroying old test database..." 470 self._destroy_test_schemas(verbosity, schemas, cursor) 392 471 cursor.execute("DROP DATABASE %s" % qn(test_database_name)) 393 472 if verbosity >= 1: 394 473 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 173 173 return name # Quoting once is enough. 174 174 return "`%s`" % name 175 175 176 def prep_db_table(self, db_schema, db_table): 177 qn = self.quote_name 178 if db_schema: 179 return "%s.%s" % (qn(db_schema), qn(db_table)) 180 else: 181 return qn(db_table) 182 183 def prep_db_index(self, db_schema, db_index): 184 return self.prep_db_table(db_schema, db_index) 185 176 186 def random_function_sql(self): 177 187 return 'RAND()' 178 188 … … 182 192 # to clear all tables of all data 183 193 if tables: 184 194 sql = ['SET FOREIGN_KEY_CHECKS = 0;'] 185 for tablein tables:186 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self. quote_name(table))))195 for (schema, table) in tables: 196 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table)))) 187 197 sql.append('SET FOREIGN_KEY_CHECKS = 1;') 188 198 189 199 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements 190 200 # to reset sequence indices 191 sql.extend(["%s %s %s %s %s;" % \ 192 (style.SQL_KEYWORD('ALTER'), 193 style.SQL_KEYWORD('TABLE'), 194 style.SQL_TABLE(self.quote_name(sequence['table'])), 195 style.SQL_KEYWORD('AUTO_INCREMENT'), 196 style.SQL_FIELD('= 1'), 197 ) for sequence in sequences]) 201 for sequence_info in sequences: 202 schema_name = sequence_info['schema'] 203 table_name = self.prep_db_table(schema_name, sequence_info['table']) 204 sql.append("%s %s %s %s %s;" % \ 205 (style.SQL_KEYWORD('ALTER'), 206 style.SQL_KEYWORD('TABLE'), 207 style.SQL_TABLE(table_name), 208 style.SQL_KEYWORD('AUTO_INCREMENT'), 209 style.SQL_FIELD('= 1'), 210 )) 198 211 return sql 199 212 else: 200 213 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 63 63 field.rel.to._meta.db_table, field.rel.to._meta.pk.column) 64 64 ] 65 65 return table_output, deferred 66 67 def default_schema(self): 68 return settings.DATABASE_NAME 69 70 def sql_create_schema(self, schema, style): 71 """ 72 Returns the SQL required to create a single schema. 73 In MySQL schemas are synonymous to databases 74 """ 75 qn = self.connection.ops.quote_name 76 output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema)) 77 return output 78 79 def sql_destroy_schema(self, schema, style): 80 """" 81 Returns the SQL required to destroy a single schema. 82 """ 83 qn = self.connection.ops.quote_name 84 output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema)) 85 return output -
django/db/backends/mysql/introspection.py
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
a b 33 33 cursor.execute("SHOW TABLES") 34 34 return [row[0] for row in cursor.fetchall()] 35 35 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 36 44 def get_table_description(self, cursor, table_name): 37 45 "Returns a description of the table, with the DB-API cursor.description interface." 38 46 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 55 55 class DatabaseOperations(BaseDatabaseOperations): 56 56 compiler_module = "django.db.backends.oracle.compiler" 57 57 58 def autoinc_sql(self, table, column):58 def autoinc_sql(self, schema, table, column): 59 59 # To simulate auto-incrementing primary keys in Oracle, we have to 60 60 # create a sequence and a trigger. 61 61 sq_name = get_sequence_name(table) 62 62 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) 64 66 col_name = self.quote_name(column) 65 67 sequence_sql = """ 66 68 DECLARE … … 69 71 SELECT COUNT(*) INTO i FROM USER_CATALOG 70 72 WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE'; 71 73 IF i = 0 THEN 72 EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';74 EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s'; 73 75 END IF; 74 76 END; 75 77 /""" % locals() 76 78 trigger_sql = """ 77 CREATE OR REPLACE TRIGGER "%(tr_name)s"79 CREATE OR REPLACE TRIGGER %(tr_qname)s 78 80 BEFORE INSERT ON %(tbl_name)s 79 81 FOR EACH ROW 80 82 WHEN (new.%(col_name)s IS NULL) 81 83 BEGIN 82 SELECT "%(sq_name)s".nextval84 SELECT %(sq_qname)s.nextval 83 85 INTO :new.%(col_name)s FROM dual; 84 86 END; 85 87 /""" % locals() … … 156 158 def deferrable_sql(self): 157 159 return " DEFERRABLE INITIALLY DEFERRED" 158 160 159 def drop_sequence_sql(self, table): 160 return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) 161 def drop_sequence_sql(self, schema, table): 162 sequence_name = self.prep_db_table(schema, get_sequence_name(table)) 163 return "DROP SEQUENCE %s;" % sequence_name 161 164 162 165 def fetch_returned_insert_id(self, cursor): 163 166 return long(cursor._insert_id_var.getvalue()) … … 168 171 else: 169 172 return "%s" 170 173 171 def last_insert_id(self, cursor, table_name, pk_name):172 sq_name = get_sequence_name(table_name)173 cursor.execute('SELECT "%s".currval FROM dual' % sq_name)174 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 175 sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name)) 176 cursor.execute('SELECT %s.currval FROM dual' % sq_name) 174 177 return cursor.fetchone()[0] 175 178 176 179 def lookup_cast(self, lookup_type): … … 181 184 def max_name_length(self): 182 185 return 30 183 186 187 def prep_db_table(self, db_schema, db_table): 188 qn = self.quote_name 189 if db_schema: 190 return "%s.%s" % (qn(db_schema), qn(db_table)) 191 else: 192 return qn(db_table) 193 194 def prep_db_index(self, db_schema, db_index): 195 return self.prep_db_table(db_schema, db_index) 196 184 197 def prep_for_iexact_query(self, x): 185 198 return x 186 199 … … 231 244 def sql_flush(self, style, tables, sequences): 232 245 # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 233 246 # 'TRUNCATE z;'... style SQL statements 247 sql = [] 234 248 if tables: 235 249 # Oracle does support TRUNCATE, but it seems to get us into 236 250 # FK referential trouble, whereas DELETE FROM table works. 237 sql = ['%s %s %s;' % \ 238 (style.SQL_KEYWORD('DELETE'), 239 style.SQL_KEYWORD('FROM'), 240 style.SQL_FIELD(self.quote_name(table))) 241 for table in tables] 251 for schema, table in tables: 252 table = self.prep_db_table(schema, table) 253 sql.append('%s %s %s;' % \ 254 (style.SQL_KEYWORD('DELETE'), 255 style.SQL_KEYWORD('FROM'), 256 style.SQL_FIELD(table))) 242 257 # Since we've just deleted all the rows, running our sequence 243 258 # ALTER code will reset the sequence to 0. 244 259 for sequence_info in sequences: 245 sequence_name = get_sequence_name(sequence_info['table']) 246 table_name = self.quote_name(sequence_info['table']) 260 schema_name = sequence_info['schema'] 261 sequence_name = self.prep_db_table(schema_name, 262 get_sequence_name(sequence_info['table'])) 263 table_name = self.prep_db_table(schema_name, 264 sequence_info['table']) 247 265 column_name = self.quote_name(sequence_info['column'] or 'id') 248 266 query = _get_sequence_reset_sql() % {'sequence': sequence_name, 249 267 'table': table_name, 250 268 'column': column_name} 251 269 sql.append(query) 252 return sql 253 else: 254 return [] 270 return sql 255 271 256 272 def sequence_reset_sql(self, style, model_list): 257 273 from django.db import models … … 260 276 for model in model_list: 261 277 for f in model._meta.local_fields: 262 278 if isinstance(f, models.AutoField): 263 table_name = self.quote_name(model._meta.db_table) 264 sequence_name = get_sequence_name(model._meta.db_table) 279 table_name = model._meta.qualified_name 280 sequence_name = self.prep_db_table(model._meta.db_schema, 281 get_sequence_name(model._meta.db_table)) 265 282 column_name = self.quote_name(f.column) 266 283 output.append(query % {'sequence': sequence_name, 267 284 'table': table_name, … … 271 288 break 272 289 for f in model._meta.many_to_many: 273 290 if not f.rel.through: 274 table_name = self.quote_name(f.m2m_db_table()) 275 sequence_name = get_sequence_name(f.m2m_db_table()) 291 table_name = self.quote_name(f.m2m_qualified_name()) 292 sequence_name = self.prep_db_table(f.m2m_db_schema(), 293 get_sequence_name(f.m2m_db_table())) 276 294 column_name = self.quote_name('id') 277 295 output.append(query % {'sequence': sequence_name, 278 296 'table': table_name, … … 646 664 BEGIN 647 665 LOCK TABLE %(table)s IN SHARE MODE; 648 666 SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; 649 SELECT "%(sequence)s".nextval INTO cval FROM dual;667 SELECT %(sequence)s.nextval INTO cval FROM dual; 650 668 cval := startvalue - cval; 651 669 IF cval != 0 THEN 652 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"MINVALUE 0 INCREMENT BY '||cval;653 SELECT "%(sequence)s".nextval INTO cval FROM dual;654 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"INCREMENT BY 1';670 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval; 671 SELECT %(sequence)s.nextval INTO cval FROM dual; 672 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1'; 655 673 END IF; 656 674 COMMIT; 657 675 END; -
django/db/backends/oracle/creation.py
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
a b 39 39 'URLField': 'VARCHAR2(%(max_length)s)', 40 40 } 41 41 42 def sql_create_schema(self, schema, style, password=None, 43 tablespace=None, temp_tablespace=None): 44 qn = self.connection.ops.quote_name 45 lock_account = (password is None) 46 if lock_account: 47 password = schema 48 output = [] 49 output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'), 50 qn(schema), 51 style.SQL_KEYWORD('IDENTIFIED BY'), 52 qn(password))) 53 if tablespace: 54 output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'), 55 qn(tablespace))) 56 if temp_tablespace: 57 output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'), 58 qn(temp_tablespace))) 59 if lock_account: 60 output.append(style.SQL_KEYWORD('ACCOUNT LOCK')) 61 return '\n'.join(output) 62 63 def sql_destroy_schema(self, schema, style): 64 qn = self.connection.ops.quote_name 65 return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema), 66 style.SQL_KEYWORD('CASCADE')) 67 42 68 remember = {} 43 69 44 def _create_test_db(self, verbosity=1, autoclobber=False ):70 def _create_test_db(self, verbosity=1, autoclobber=False, schema): 45 71 TEST_NAME = self._test_database_name() 46 72 TEST_USER = self._test_database_user() 47 73 TEST_PASSWD = self._test_database_passwd() … … 174 200 DEFAULT TABLESPACE %(tblspace)s 175 201 TEMPORARY TABLESPACE %(tblspace_temp)s 176 202 """, 177 """GRANT CONNECT, RESOURCE TO %(user)s""",203 """GRANT CONNECT, RESOURCE, CREATE USER, DROP USER, CREATE ANY TABLE, ALTER ANY TABLE, CREATE ANY INDEX, CREATE ANY SEQUENCE, CREATE ANY TRIGGER, SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE TO %(user)s""", 178 204 ] 179 205 self._execute_statements(cursor, statements, parameters, verbosity) 180 206 -
django/db/backends/oracle/introspection.py
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
a b 119 119 for row in cursor.fetchall(): 120 120 indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]} 121 121 return indexes 122 123 def schema_name_converter(self, name): 124 """Convert to lowercase for case-sensitive schema name comparison.""" 125 return name.lower() 126 127 def get_schema_list(self, cursor): 128 "Returns a list of schemas that exist in the database." 129 sql = """ 130 select distinct username 131 from all_users, all_objects 132 where username = owner 133 """ 134 cursor.execute(sql) 135 return [schema.lower() for (schema,) in cursor] 136 137 def get_schema_table_list(self, cursor, schema): 138 "Returns a list of tables in a specific schema." 139 sql = """ 140 select table_name from all_tables 141 where owner = upper(%s) 142 """ 143 cursor.execute(sql, [schema]) 144 return [table.lower() for (table,) in cursor] -
django/db/backends/postgresql/base.py
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
a b 80 80 81 81 class DatabaseFeatures(BaseDatabaseFeatures): 82 82 uses_savepoints = True 83 default_schema_name = u'public' 83 84 84 85 class DatabaseWrapper(BaseDatabaseWrapper): 85 86 operators = { -
django/db/backends/postgresql/creation.py
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
a b 51 51 tablespace_sql = '' 52 52 53 53 def get_index_sql(index_name, opclass=''): 54 index_name = truncate_name(index_name, self.connection.ops.max_name_length()) 55 index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name) 54 56 return (style.SQL_KEYWORD('CREATE INDEX') + ' ' + 55 style.SQL_TABLE( qn(truncate_name(index_name,self.connection.ops.max_name_length()))) + ' ' +57 style.SQL_TABLE(index_name) + ' ' + 56 58 style.SQL_KEYWORD('ON') + ' ' + 57 style.SQL_TABLE( qn(db_table)) + ' ' +59 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 58 60 "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) + 59 61 "%s;" % tablespace_sql) 60 62 -
django/db/backends/postgresql/introspection.py
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
a b 31 31 AND pg_catalog.pg_table_is_visible(c.oid)""") 32 32 return [row[0] for row in cursor.fetchall()] 33 33 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 34 52 def get_table_description(self, cursor, table_name): 35 53 "Returns a description of the table, with the DB-API cursor.description interface." 36 54 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 53 53 return 'HOST(%s)' 54 54 return '%s' 55 55 56 def last_insert_id(self, cursor, table_name, pk_name):56 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 57 57 # Use pg_get_serial_sequence to get the underlying sequence name 58 58 # from the table name and column name (available since PostgreSQL 8) 59 table_name = self.prep_db_table(schema_name, table_name) 59 60 cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % (table_name, pk_name)) 60 61 return cursor.fetchone()[0] 61 62 … … 67 68 return name # Quoting once is enough. 68 69 return '"%s"' % name 69 70 71 def prep_db_table(self, db_schema, db_table): 72 qn = self.quote_name 73 if db_schema: 74 return "%s.%s" % (qn(db_schema), qn(db_table)) 75 else: 76 return qn(db_table) 77 70 78 def sql_flush(self, style, tables, sequences): 71 79 if tables: 80 qnames = [self.prep_db_table(schema, table) 81 for (schema, table) in tables] 72 82 if self.postgres_version[0:2] >= (8,1): 73 83 # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* 74 84 # in order to be able to truncate tables referenced by a foreign … … 76 86 # statement. 77 87 sql = ['%s %s;' % \ 78 88 (style.SQL_KEYWORD('TRUNCATE'), 79 style.SQL_FIELD(', '.join( [self.quote_name(table) for table in tables]))89 style.SQL_FIELD(', '.join(qnames)) 80 90 )] 81 91 else: 82 92 # Older versions of Postgres can't do TRUNCATE in a single call, so … … 84 94 sql = ['%s %s %s;' % \ 85 95 (style.SQL_KEYWORD('DELETE'), 86 96 style.SQL_KEYWORD('FROM'), 87 style.SQL_FIELD( self.quote_name(table))88 ) for table in tables]97 style.SQL_FIELD(qname) 98 ) for qname in qnames] 89 99 90 100 # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements 91 101 # to reset sequence indices 92 102 for sequence_info in sequences: 103 schema_name = sequence_info['schema'] 93 104 table_name = sequence_info['table'] 94 105 column_name = sequence_info['column'] 95 106 if not (column_name and len(column_name) > 0): 96 107 # This will be the case if it's an m2m using an autogenerated 97 108 # intermediate table (see BaseDatabaseIntrospection.sequence_list) 98 109 column_name = 'id' 110 table_name = self.prep_db_table(schema_name, table_name) 99 111 sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ 100 112 (style.SQL_KEYWORD('SELECT'), 101 113 style.SQL_TABLE(table_name), … … 118 130 119 131 for f in model._meta.local_fields: 120 132 if isinstance(f, models.AutoField): 133 table_name = self.prep_db_table(model._meta.db_schema, model._meta.db_table) # XXX: generic schemas support. 121 134 output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 122 135 (style.SQL_KEYWORD('SELECT'), 123 style.SQL_TABLE( model._meta.db_table),124 style.SQL_FIELD(f.column), 136 style.SQL_TABLE(table_name), 137 style.SQL_FIELD(f.column), # XXX: Do we need qn() here? 125 138 style.SQL_FIELD(qn(f.column)), 126 139 style.SQL_FIELD(qn(f.column)), 127 140 style.SQL_KEYWORD('IS NOT'), 128 141 style.SQL_KEYWORD('FROM'), 129 style.SQL_TABLE( qn(model._meta.db_table))))142 style.SQL_TABLE(model._meta.qualified_name))) 130 143 break # Only one AutoField is allowed per model, so don't bother continuing. 131 144 for f in model._meta.many_to_many: 132 145 if not f.rel.through: 146 table_name = self.prep_db_table(f.m2m_db_schema(), f.m2m_db_table()) # XXX: Do we need qn(f.m2m_db_table()) here? / XXX: generic schemas support. 133 147 output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 134 148 (style.SQL_KEYWORD('SELECT'), 135 style.SQL_TABLE( model._meta.db_table),136 style.SQL_FIELD('id'), 149 style.SQL_TABLE(table_name), 150 style.SQL_FIELD('id'), # XXX: Do we need qn() here? 137 151 style.SQL_FIELD(qn('id')), 138 152 style.SQL_FIELD(qn('id')), 139 153 style.SQL_KEYWORD('IS NOT'), 140 154 style.SQL_KEYWORD('FROM'), 141 style.SQL_TABLE(qn(f.m2m_ db_table()))))155 style.SQL_TABLE(qn(f.m2m_qualified_name())))) 142 156 return output 143 157 144 158 def savepoint_create_sql(self, sid): -
django/db/backends/postgresql_psycopg2/base.py
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
a b 67 67 class DatabaseFeatures(BaseDatabaseFeatures): 68 68 needs_datetime_string_cast = False 69 69 can_return_id_from_insert = False 70 default_schema_name = u'public' 70 71 71 72 class DatabaseOperations(PostgresqlDatabaseOperations): 72 73 def last_executed_query(self, cursor, sql, params): -
django/db/backends/sqlite3/base.py
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
a b 98 98 (style.SQL_KEYWORD('DELETE'), 99 99 style.SQL_KEYWORD('FROM'), 100 100 style.SQL_FIELD(self.quote_name(table)) 101 ) for tablein tables]101 ) for (_, table) in tables] 102 102 # Note: No requirement for reset of auto-incremented indices (cf. other 103 103 # sql_flush() implementations). Just return SQL at this point 104 104 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 38 38 "SQLite3 doesn't support constraints" 39 39 return [] 40 40 41 def _create_test_db(self, verbosity, autoclobber ):41 def _create_test_db(self, verbosity, autoclobber, schemas): 42 42 test_database_name = self.connection.settings_dict['TEST_NAME'] 43 43 if test_database_name and test_database_name != ":memory:": 44 44 # Erase the old test database -
django/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
a b 872 872 if isinstance(self.rel.to, basestring): 873 873 target = self.rel.to 874 874 else: 875 target = self.rel.to._meta. db_table875 target = self.rel.to._meta.qualified_name 876 876 cls._meta.duplicate_targets[self.column] = (target, "o2m") 877 877 878 878 def contribute_to_related_class(self, cls, related): … … 961 961 to = to.lower() 962 962 meta = type('Meta', (object,), { 963 963 'db_table': field._get_m2m_db_table(klass._meta), 964 'db_schema': field._get_m2m_db_schema(klass._meta), 964 965 'managed': managed, 965 966 'auto_created': klass, 966 967 'app_label': klass._meta.app_label, … … 992 993 through=kwargs.pop('through', None)) 993 994 994 995 self.db_table = kwargs.pop('db_table', None) 996 self.db_schema = kwargs.pop('db_schema', '') 995 997 if kwargs['rel'].through is not None: 996 998 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 997 999 … … 1013 1015 return util.truncate_name('%s_%s' % (opts.db_table, self.name), 1014 1016 connection.ops.max_name_length()) 1015 1017 1018 def _get_m2m_db_schema(self, opts): 1019 "Function that can be curried to provide the m2m schema name for this relation" 1020 if self.rel.through is not None and self.rel.through._meta.db_schema: 1021 return self.rel.through._meta.db_schema 1022 return self.db_schema 1023 1024 def _get_m2m_qualified_name(self, opts): 1025 "Function that can be curried to provide the qualified m2m table name for this relation" 1026 schema = self._get_m2m_db_schema(opts) 1027 table = self._get_m2m_db_table(opts) 1028 return connection.ops.prep_db_table(schema, table) 1029 1016 1030 def _get_m2m_attr(self, related, attr): 1017 1031 "Function that can be curried to provide the source accessor or DB column name for the m2m table" 1018 1032 cache_attr = '_m2m_%s_cache' % attr … … 1102 1116 1103 1117 # Set up the accessor for the m2m table name for the relation 1104 1118 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 1119 self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta) 1120 self.m2m_qualified_name = curry(self._get_m2m_qualified_name, 1121 cls._meta) 1105 1122 1106 1123 # Populate some necessary rel arguments so that cross-app relations 1107 1124 # work correctly. … … 1113 1130 if isinstance(self.rel.to, basestring): 1114 1131 target = self.rel.to 1115 1132 else: 1116 target = self.rel.to._meta. db_table1133 target = self.rel.to._meta.qualified_name 1117 1134 cls._meta.duplicate_targets[self.column] = (target, "m2m") 1118 1135 1119 1136 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 17 17 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 18 18 'unique_together', 'permissions', 'get_latest_by', 19 19 'order_with_respect_to', 'app_label', 'db_tablespace', 20 'abstract', 'managed', 'proxy', 'auto_created' )20 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema') 21 21 22 22 class Options(object): 23 23 def __init__(self, meta, app_label=None): … … 26 26 self.module_name, self.verbose_name = None, None 27 27 self.verbose_name_plural = None 28 28 self.db_table = '' 29 #self.db_schema = settings.DATABASE_SCHEMA 30 self.db_schema = None 31 self.qualified_name = '' 29 32 self.ordering = [] 30 33 self.unique_together = [] 31 34 self.permissions = [] … … 51 54 self.concrete_managers = [] 52 55 53 56 def contribute_to_class(self, cls, name): 54 from django.db import connection 57 from django.db import connections, router 55 58 from django.db.backends.util import truncate_name 59 conn = connections[router.db_for_read(cls)] 56 60 57 61 cls._meta = self 58 62 self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS … … 60 64 self.object_name = cls.__name__ 61 65 self.module_name = self.object_name.lower() 62 66 self.verbose_name = get_verbose_name(self.object_name) 67 self.db_schema = conn.settings_dict['SCHEMA'] 63 68 64 69 # Next, apply any overridden values from 'class Meta'. 65 70 if self.meta: … … 98 103 # If the db_table wasn't provided, use the app_label + module_name. 99 104 if not self.db_table: 100 105 self.db_table = "%s_%s" % (self.app_label, self.module_name) 101 self.db_table = truncate_name(self.db_table, conn ection.ops.max_name_length())106 self.db_table = truncate_name(self.db_table, conn.ops.max_name_length()) 102 107 108 # Construct qualified table name. 109 self.qualified_name = conn.ops.prep_db_table(self.db_schema, 110 self.db_table) 111 if self.qualified_name == conn.ops.quote_name(self.db_table): 112 # If unchanged, the backend doesn't support schemas. 113 self.db_schema = '' 103 114 def _prepare(self, model): 104 115 if self.order_with_respect_to: 105 116 self.order_with_respect_to = self.get_field(self.order_with_respect_to) … … 173 184 self.pk = target._meta.pk 174 185 self.proxy_for_model = target 175 186 self.db_table = target._meta.db_table 187 self.db_schema = target._meta.db_schema 188 self.qualified_name = target._meta.qualified_name 176 189 177 190 def __repr__(self): 178 191 return '<Options for %s>' % self.object_name -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
a b 21 21 might not have all the pieces in place at that time. 22 22 """ 23 23 if not self.query.tables: 24 self.query.join((None, self.query.model._meta. db_table, None, None))24 self.query.join((None, self.query.model._meta.qualified_name, None, None)) 25 25 if (not self.query.select and self.query.default_cols and not 26 26 self.query.included_inherited_models): 27 27 self.query.setup_inherited_models() … … 248 248 alias = start_alias 249 249 else: 250 250 link_field = opts.get_ancestor_link(model) 251 alias = self.query.join((start_alias, model._meta. db_table,251 alias = self.query.join((start_alias, model._meta.qualified_name, 252 252 link_field.column, model._meta.pk.column)) 253 253 seen[model] = alias 254 254 else: … … 449 449 result.append('%s%s%s' % (connector, qn(name), alias_str)) 450 450 first = False 451 451 for t in self.query.extra_tables: 452 alias, unused = self.query.table_alias( t)452 alias, unused = self.query.table_alias(qn(t)) 453 453 # Only add the alias if it's not already present (the table_alias() 454 454 # calls increments the refcount, so an alias refcount of one means 455 455 # this is the only reference. … … 529 529 # what "used" specifies). 530 530 avoid = avoid_set.copy() 531 531 dupe_set = orig_dupe_set.copy() 532 table = f.rel.to._meta. db_table532 table = f.rel.to._meta.qualified_name 533 533 promote = nullable or f.null 534 534 if model: 535 535 int_opts = opts … … 550 550 ())) 551 551 dupe_set.add((opts, lhs_col)) 552 552 int_opts = int_model._meta 553 alias = self.query.join((alias, int_opts. db_table, lhs_col,553 alias = self.query.join((alias, int_opts.qualified_name, lhs_col, 554 554 int_opts.pk.column), exclusions=used, 555 555 promote=promote) 556 556 alias_chain.append(alias) … … 602 602 # what "used" specifies). 603 603 avoid = avoid_set.copy() 604 604 dupe_set = orig_dupe_set.copy() 605 table = model._meta. db_table605 table = model._meta.qualified_name 606 606 607 607 int_opts = opts 608 608 alias = root_alias … … 625 625 dupe_set.add((opts, lhs_col)) 626 626 int_opts = int_model._meta 627 627 alias = self.query.join( 628 (alias, int_opts. db_table, lhs_col, int_opts.pk.column),628 (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column), 629 629 exclusions=used, promote=True, reuse=used 630 630 ) 631 631 alias_chain.append(alias) … … 766 766 # going to be column names (so we can avoid the extra overhead). 767 767 qn = self.connection.ops.quote_name 768 768 opts = self.query.model._meta 769 result = ['INSERT INTO %s' % qn(opts.db_table)]769 result = ['INSERT INTO %s' % opts.qualified_name] 770 770 result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns])) 771 771 values = [self.placeholder(*v) for v in self.query.values] 772 772 result.append('VALUES (%s)' % ', '.join(values)) 773 773 params = self.query.params 774 774 if self.return_id and self.connection.features.can_return_id_from_insert: 775 col = "%s.%s" % ( qn(opts.db_table), qn(opts.pk.column))775 col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column)) 776 776 r_fmt, r_params = self.connection.ops.return_insert_id() 777 777 result.append(r_fmt % col) 778 778 params = params + r_params … … 786 786 if self.connection.features.can_return_id_from_insert: 787 787 return self.connection.ops.fetch_returned_insert_id(cursor) 788 788 return self.connection.ops.last_insert_id(cursor, 789 self.query.model._meta.db_table, self.query.model._meta.pk.column) 789 self.query.model._meta.db_schema, self.query.model._meta.db_table, 790 self.query.model._meta.pk.column) 790 791 791 792 792 793 class SQLDeleteCompiler(SQLCompiler): -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
a b 593 593 Callback used by deferred_to_columns(). The "target" parameter should 594 594 be a set instance. 595 595 """ 596 table = model._meta. db_table596 table = model._meta.qualified_name 597 597 if table not in target: 598 598 target[table] = set() 599 599 for field in fields: … … 775 775 alias = self.tables[0] 776 776 self.ref_alias(alias) 777 777 else: 778 alias = self.join((None, self.model._meta.db_table, None, None)) 778 alias = self.join((None, self.model._meta.qualified_name, 779 None, None)) 779 780 return alias 780 781 781 782 def count_active_tables(self): … … 888 889 seen[model] = root_alias 889 890 else: 890 891 link_field = opts.get_ancestor_link(model) 891 seen[model] = self.join((root_alias, model._meta.db_table, 892 seen[model] = self.join((root_alias, 893 model._meta.qualified_name, 892 894 link_field.column, model._meta.pk.column)) 893 895 self.included_inherited_models = seen 894 896 … … 1213 1215 (id(opts), lhs_col), ())) 1214 1216 dupe_set.add((opts, lhs_col)) 1215 1217 opts = int_model._meta 1216 alias = self.join((alias, opts.db_table, lhs_col, 1217 opts.pk.column), exclusions=exclusions) 1218 alias = self.join((alias, opts.qualified_name, 1219 lhs_col, opts.pk.column), 1220 exclusions=exclusions) 1218 1221 joins.append(alias) 1219 1222 exclusions.add(alias) 1220 1223 for (dupe_opts, dupe_col) in dupe_set: … … 1239 1242 (table1, from_col1, to_col1, table2, from_col2, 1240 1243 to_col2, opts, target) = cached_data 1241 1244 else: 1242 table1 = field.m2m_ db_table()1245 table1 = field.m2m_qualified_name() 1243 1246 from_col1 = opts.pk.column 1244 1247 to_col1 = field.m2m_column_name() 1245 1248 opts = field.rel.to._meta 1246 table2 = opts. db_table1249 table2 = opts.qualified_name 1247 1250 from_col2 = field.m2m_reverse_name() 1248 1251 to_col2 = opts.pk.column 1249 1252 target = opts.pk … … 1270 1273 else: 1271 1274 opts = field.rel.to._meta 1272 1275 target = field.rel.get_related_field() 1273 table = opts. db_table1276 table = opts.qualified_name 1274 1277 from_col = field.column 1275 1278 to_col = target.column 1276 1279 orig_opts._join_cache[name] = (table, from_col, to_col, … … 1292 1295 (table1, from_col1, to_col1, table2, from_col2, 1293 1296 to_col2, opts, target) = cached_data 1294 1297 else: 1295 table1 = field.m2m_ db_table()1298 table1 = field.m2m_qualified_name() 1296 1299 from_col1 = opts.pk.column 1297 1300 to_col1 = field.m2m_reverse_name() 1298 1301 opts = orig_field.opts 1299 table2 = opts. db_table1302 table2 = opts.qualified_name 1300 1303 from_col2 = field.m2m_column_name() 1301 1304 to_col2 = opts.pk.column 1302 1305 target = opts.pk … … 1319 1322 local_field = opts.get_field_by_name( 1320 1323 field.rel.field_name)[0] 1321 1324 opts = orig_field.opts 1322 table = opts. db_table1325 table = opts.qualified_name 1323 1326 from_col = local_field.column 1324 1327 to_col = field.column 1325 1328 target = opts.pk … … 1575 1578 else: 1576 1579 opts = self.model._meta 1577 1580 if not self.select: 1578 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column), 1581 count = self.aggregates_module.Count((self.join((None, 1582 opts.qualified_name, None, None)), opts.pk.column), 1579 1583 is_summary=True, distinct=True) 1580 1584 else: 1581 1585 # 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 38 38 field = self.model._meta.pk 39 39 where.add((Constraint(None, field.column, field), 'in', 40 40 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 41 self.do_query(self.model._meta. db_table, where, using=using)41 self.do_query(self.model._meta.qualified_name, where, using=using) 42 42 43 43 class UpdateQuery(Query): 44 44 """ -
django/db/utils.py
diff --git a/django/db/utils.py b/django/db/utils.py
a b 79 79 conn.setdefault('TEST_NAME', None) 80 80 conn.setdefault('TEST_MIRROR', None) 81 81 conn.setdefault('TIME_ZONE', settings.TIME_ZONE) 82 for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT' ):82 for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT', 'SCHEMA'): 83 83 conn.setdefault(setting, '') 84 84 85 85 def __getitem__(self, alias): -
docs/ref/models/options.txt
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
a b 62 62 aren't allowed in Python variable names -- notably, the hyphen -- that's OK. 63 63 Django quotes column and table names behind the scenes. 64 64 65 .. _db_schema: 66 67 ``db_schema`` 68 ------------- 69 70 .. attribute:: Options.db_schema 71 72 .. versionadded:: 1.3 73 74 The name of the database schema to use for the model. If the backend 75 doesn't support multiple schemas, this option is ignored. 76 77 If this is used then Django will prefix the model table names with the schema 78 name. For example with MySQL Django would use ``db_schema + '.' + db_table``. 79 Be aware that PostgreSQL supports different schemas within the database. 80 MySQL solves the same thing by treating it as just another database. 81 65 82 ``db_tablespace`` 66 83 ----------------- 67 84 -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
a b 317 317 The port to use when connecting to the database. An empty string means the 318 318 default port. Not used with SQLite. 319 319 320 .. setting:: SCHEMA 321 322 SCHEMA 323 ~~~~~~ 324 325 .. versionadded:: 1.3 326 327 Default: ``''`` (Empty string) 328 329 The name of the database schema to use for models. If the backend 330 doesn't support multiple schemas, this option is ignored. An empty 331 string means the default schema. 332 333 If this is used then Django will prefix any table names with the schema name. 334 The schema can be overriden on a per-model basis, for details see 335 :ref:`db_schema`. 336 320 337 .. setting:: USER 321 338 322 339 USER -
docs/topics/db/models.txt
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
a b 629 629 verbose_name_plural = "oxen" 630 630 631 631 Model 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`), 633 custom schema for the tables (:attr:`~Options.db_schema`), or 633 634 human-readable singular and plural names (:attr:`~Options.verbose_name` and 634 635 :attr:`~Options.verbose_name_plural`). None are required, and adding ``class 635 636 Meta`` to a model is completely optional. -
new file tests/modeltests/schemas/__init__.py
diff --git a/tests/modeltests/schemas/__init__.py b/tests/modeltests/schemas/__init__.py new file mode 100644
- + 1 # -
new file tests/modeltests/schemas/fixtures/schema_tests_fixtures.json
diff --git a/tests/modeltests/schemas/fixtures/schema_tests_fixtures.json b/tests/modeltests/schemas/fixtures/schema_tests_fixtures.json new file mode 100644
- + 1 [ 2 { 3 "pk": 1, 4 "model": "schemas.tag", 5 "fields": { 6 "name": "Test" 7 } 8 }, 9 { 10 "pk": 1, 11 "model": "schemas.blog", 12 "fields": { 13 "name":"Test" 14 } 15 }, 16 { 17 "pk": 1, 18 "model": "schemas.entry", 19 "fields": { 20 "blog": 1, 21 "title": "Test entry", 22 "tags": [1] 23 } 24 }, 25 { 26 "pk": 1, 27 "model": "schemas.comment", 28 "fields": { 29 "entry": 1, 30 "text": "nice entry" 31 } 32 }, 33 { 34 "pk": 2, 35 "model": "schemas.comment", 36 "fields": { 37 "entry": 1, 38 "text": "typical spam comment" 39 } 40 } 41 42 43 ] -
new file tests/modeltests/schemas/models.py
diff --git a/tests/modeltests/schemas/models.py b/tests/modeltests/schemas/models.py new file mode 100644
- + 1 # coding: utf-8 2 3 from django.db import models 4 5 class Category(models.Model): 6 name = models.CharField(max_length=50) 7 8 9 class Blog(models.Model): 10 "Model in default schema" 11 name = models.CharField(max_length=50) 12 13 14 class 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 26 class 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 35 class Tag(models.Model): 36 "Used to test m2m relations" 37 name=models.CharField(max_length=20) 38 entries=models.ManyToManyField(Entry) -
new file tests/modeltests/schemas/tests.py
diff --git a/tests/modeltests/schemas/tests.py b/tests/modeltests/schemas/tests.py new file mode 100644
- + 1 import unittest 2 from models import Blog, Entry, Comment, Tag, Category 3 4 from django.conf import settings 5 from django.db import connection, models, DEFAULT_DB_ALIAS 6 7 class BasicSchemaTests(unittest.TestCase): 8 9 def setUp(self): 10 # Test with actual data. Nothing in there yet 11 self.assertEqual(Blog.objects.count(), 0) 12 13 # Create a blog 14 self.b = Blog(name='Test') 15 self.b.save() 16 self.assertEqual(self.b.id, 1) 17 18 # Create an entry 19 self.e = Entry(blog=self.b, title='Test entry') 20 self.e.save() 21 self.assertEqual(self.e.id, 1) 22 23 # Create comments 24 c1 = Comment(entry=self.e, text='nice entry') 25 c1.save() 26 c2 = Comment(entry=self.e, text='really like it') 27 c2.save() 28 29 def test_schema_support_regression(self): 30 # Retrieve the stuff again. 31 b2 = Blog.objects.get(id=self.b.id) 32 self.assertTrue(self.b==b2) 33 34 self.assertEqual(b2.entry_set.count(), 1) 35 #[<Entry: Entry object>] 36 37 # Make sure we don't break many to many relations 38 t = Tag(name="test") 39 t.save() 40 t.entries.add(self.e) 41 42 t2 = Tag.objects.get(id=t.id) 43 self.assertEqual(t2.entries.count(), 1) 44 45 # Test if we support schemas and can find the table if so 46 if self.e._meta.db_schema: 47 tables = connection.introspection.schema_table_names(self.e._meta.db_schema) 48 else: 49 tables = connection.introspection.table_names() 50 self.assertTrue(connection.introspection.table_name_converter(self.e._meta.db_table) in tables) 51 52 53 class SchemaTests(unittest.TestCase): 54 fixtures = ['schema_tests_fixtures',] 55 VALID_ENGINES = ['postgresql_psycopg2', 'postgresql', 'mysql', 'oracle'] 56 VALID_ENGINES = ['django.db.backends.%s' %b for b in VALID_ENGINES] 57 58 def test_meta_information(self): 59 e = Entry.objects.get(id=1) 60 b = Blog.objects.get(id=1) 61 if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in self.VALID_ENGINES: 62 self.assertEqual('test_schema', e._meta.db_schema) 63 if settings.DATABASE_SCHEMA: 64 self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema) 65 else: 66 self.assertFalse(b._meta.db_schema) 67 else: 68 self.assertFalse(e._meta.db_schema) # No model schema 69 self.assertFalse(b._meta.db_schema) # No global schema 70 71 def test_schema_in_m2m_declaring_model(self): 72 e = Entry.objects.get(id=1) 73 c = Category(name='Test') 74 c.save() 75 e.categories = [c] 76 77 categories = e.categories.filter(name='Test') 78 79 self.assertEqual(1, len(categories)) -
tests/regressiontests/backends/tests.py
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
a b 124 124 VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 125 125 VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through 126 126 tables = [ 127 VLM._meta.db_table,128 VLM_m2m._meta.db_table,127 (VLM._meta.db_schema, VLM._meta.db_table), 128 (VLM_m2m._meta.db_schema, VLM_m2m._meta.db_table), 129 129 ] 130 130 sequences = [ 131 131 { 132 132 'column': VLM._meta.pk.column, 133 'table': VLM._meta.db_table 133 'table': VLM._meta.db_table, 134 'schema': VLM._meta.db_schema, 134 135 }, 135 136 ] 136 137 cursor = connection.cursor() -
tests/regressiontests/introspection/tests.py
diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
a b 58 58 59 59 def test_sequence_list(self): 60 60 sequences = connection.introspection.sequence_list() 61 expected = {'table': Reporter._meta.db_table, 'column': 'id' }61 expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''} 62 62 self.assert_(expected in sequences, 63 63 'Reporter sequence not found in sequence_list()') 64 64