Ticket #6148: 6148-generic-schema-support-r12426.diff
File 6148-generic-schema-support-r12426.diff, 73.0 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
a b 139 139 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 140 140 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 141 141 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 142 DATABASE_SCHEMA = '' # Set to empty string for default. 142 143 143 144 # New format 144 145 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 51 51 cursor = connection.cursor() 52 52 53 53 # Get a list of already installed *models* so that references work right. 54 tables = connection.introspection.table_names()54 tables = [('', tn) for tn in connection.introspection.table_names()] 55 55 seen_models = connection.introspection.installed_models(tables) 56 seen_schemas = set() 56 57 created_models = set() 57 58 pending_references = {} 58 59 … … 67 68 # Create the tables for each model 68 69 for app_name, model_list in manifest.items(): 69 70 for model in model_list: 70 # Create the model's database table, if it doesn't already exist. 71 # Add model-defined schema tables if any. 72 db_schema = model._meta.db_schema 73 if db_schema: 74 db_schema = connection.introspection.schema_name_converter(db_schema) 75 if db_schema not in seen_schemas: 76 tables += [(db_schema, tn) for tn in 77 connection.introspection.schema_table_names(db_schema)] 78 seen_schemas.add(db_schema) 79 80 # Create the model's database table, 81 # if it doesn't already exist. 71 82 if verbosity >= 2: 72 83 print "Processing %s.%s model" % (app_name, model._meta.object_name) 73 84 opts = model._meta 74 if (connection.introspection.table_name_converter(opts.db_table) in tables or 75 (opts.auto_created and 76 connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)): 85 schema_table = (db_schema, connection.introspection.table_name_converter(opts.db_table)) 86 if schema_table in tables or \ 87 (opts.auto_created and \ 88 (db_schema, 89 connection.introspection.table_name_converter(opts.auto_created._meta.db_table)) 90 in tables): 77 91 continue 78 92 sql, references = connection.creation.sql_create_model(model, self.style, seen_models) 79 93 seen_models.add(model) … … 87 101 print "Creating table %s" % model._meta.db_table 88 102 for statement in sql: 89 103 cursor.execute(statement) 90 tables.append( connection.introspection.table_name_converter(model._meta.db_table))104 tables.append(schema_table) 91 105 92 106 93 107 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 71 71 72 72 # Figure out which tables already exist 73 73 if cursor: 74 table_names = connection.introspection.get_table_list(cursor) 74 table_names = [('', tn) for tn in 75 connection.introspection.get_table_list(cursor)] 75 76 else: 76 77 table_names = [] 77 78 … … 82 83 83 84 references_to_delete = {} 84 85 app_models = models.get_models(app, include_auto_created=True) 86 seen_schemas = set() 85 87 for model in app_models: 86 if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names: 88 db_schema = model._meta.db_schema 89 # Find additional tables in model-defined schemas. 90 if db_schema: 91 db_schema = connection.introspection.schema_name_converter(db_schema) 92 if db_schema not in seen_schemas: 93 table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)] 94 seen_schemas.add(db_schema) 95 schema_table = (db_schema, 96 connection.introspection.table_name_converter(model._meta.db_table)) 97 98 if cursor and schema_table in table_names: 87 99 # The table exists, so it needs to be dropped 88 100 opts = model._meta 89 101 for f in opts.local_fields: … … 93 105 to_delete.add(model) 94 106 95 107 for model in app_models: 96 if connection.introspection.table_name_converter(model._meta.db_table) in table_names: 108 db_schema = model._meta.db_schema 109 if db_schema: 110 db_schema = connection.introspection.schema_name_converter(db_schema) 111 schema_table = (db_schema, 112 connection.introspection.table_name_converter(model._meta.db_table)) 113 if schema_table in table_names: 97 114 output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style)) 98 115 99 116 # Close database connection explicitly, in case this output is being piped … … 118 135 if only_django: 119 136 tables = connection.introspection.django_table_names(only_existing=True) 120 137 else: 121 tables = connection.introspection.table_names()138 tables = [('', tn) for tn in connection.introspection.table_names()] 122 139 statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list()) 123 140 return statements 124 141 -
django/db/backends/__init__.py
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
a b 107 107 def __init__(self): 108 108 self._cache = {} 109 109 110 def autoinc_sql(self, table, column):110 def autoinc_sql(self, schema, table, column): 111 111 """ 112 112 Returns any SQL needed to support auto-incrementing primary keys, or 113 113 None if no SQL is necessary. … … 153 153 """ 154 154 return "DROP CONSTRAINT" 155 155 156 def drop_sequence_sql(self, table):156 def drop_sequence_sql(self, schema, table): 157 157 """ 158 158 Returns any SQL necessary to drop the sequence for the given table. 159 159 Returns None if no SQL is necessary. … … 214 214 215 215 return smart_unicode(sql) % u_params 216 216 217 def last_insert_id(self, cursor, table_name, pk_name):217 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 218 218 """ 219 219 Given a cursor object that has just performed an INSERT statement into 220 220 a table that has an auto-incrementing ID, returns the newly created ID. … … 288 288 """ 289 289 raise NotImplementedError() 290 290 291 def prep_db_table(self, db_schema, db_table): 292 """ 293 Prepares and formats the table name if necessary. 294 Just returns quoted db_table if not supported. 295 """ 296 return self.quote_name(db_table) 297 298 def prep_db_index(self, db_schema, db_index): 299 """ 300 Prepares and formats the table index name if necessary. 301 Just returns quoted db_index if not supported. 302 """ 303 return self.quote_name(db_index) 304 291 305 def random_function_sql(self): 292 306 """ 293 307 Returns a SQL expression that returns a random value. … … 487 501 return name 488 502 489 503 def table_names(self): 490 "Returns a list of names of all tables that exist in the d atabase."504 "Returns a list of names of all tables that exist in the default schema." 491 505 cursor = self.connection.cursor() 492 506 return self.get_table_list(cursor) 493 507 508 def schema_name_converter(self, name): 509 """Apply a conversion to the name for the purposes of comparison. 510 511 The default schema name converter is for case sensitive comparison. 512 """ 513 return name 514 515 def get_schema_list(self, cursor): 516 "Returns a list of schemas that exist in the database" 517 return [] 518 519 def get_schema_table_list(self, cursor, schema): 520 "Returns a list of tables in a specific schema" 521 return [] 522 523 def schema_names(self): 524 cursor = self.connection.cursor() 525 return self.get_schema_list(cursor) 526 527 def schema_table_names(self, schema): 528 "Returns a list of names of all tables that exist in the database schema." 529 cursor = self.connection.cursor() 530 return self.get_schema_table_list(cursor, schema) 531 494 532 def django_table_names(self, only_existing=False): 495 533 """ 496 Returns a list of all table names that have associated Django models and497 are in INSTALLED_APPS.534 Returns a list of tuples containing all schema and table names that 535 have associated Django models and are in INSTALLED_APPS. 498 536 499 If only_existing is True, the resulting list will only include the tables500 t hat actually exist in the database.537 If only_existing is True, the resulting list will only include the 538 tables that actually exist in the database. 501 539 """ 502 540 from django.db import models 503 541 tables = set() 504 for app in models.get_apps():505 for model in models.get_models(app):506 if not model._meta.managed:507 continue508 tables.add(model._meta.db_table)509 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])510 542 if only_existing: 511 tables = [t for t in tables if self.table_name_converter(t) in self.table_names()] 543 existing_tables = set([('', tn) for tn in self.table_names()]) 544 seen_schemas = set() 545 for model in models.get_models(): 546 if not model._meta.managed: 547 continue 548 db_schema = model._meta.db_schema 549 db_table = model._meta.db_table 550 if only_existing and db_schema and db_schema not in seen_schemas: 551 existing_tables.update([(db_schema, tn) for tn in 552 self.schema_table_names(db_schema)]) 553 seen_schemas.add(db_schema) 554 tables.add((model._meta.db_schema, model._meta.db_table)) 555 for f in model._meta.local_many_to_many: 556 m2m_schema = f.m2m_db_schema() 557 m2m_table = f.m2m_db_table() 558 if only_existing and m2m_schema and m2m_schema not in seen_schemas: 559 existing_tables.update([(m2m_schema, tn) for tn in 560 self.schema_table_names(m2m_schema)]) 561 seen_schemas.add(m2m_schema) 562 tables.add((m2m_schema, m2m_table)) 563 if only_existing: 564 tables = [(s, t) for (s, t) in tables 565 if (self.schema_name_converter(s), 566 self.table_name_converter(t)) in existing_tables] 512 567 return tables 513 568 514 569 def installed_models(self, tables): … … 535 590 continue 536 591 for f in model._meta.local_fields: 537 592 if isinstance(f, models.AutoField): 538 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 593 sequence_list.append({'table': model._meta.db_table, 594 'column': f.column, 595 'schema': model._meta.db_schema}) 539 596 break # Only one AutoField is allowed per model, so don't bother continuing. 540 597 541 598 for f in model._meta.local_many_to_many: 599 schema = f.m2m_db_schema() 542 600 # If this is an m2m using an intermediate table, 543 601 # we don't need to reset the sequence. 544 602 if f.rel.through is None: 545 sequence_list.append({'table': f.m2m_db_table(), 'column': None}) 603 sequence_list.append({'table': f.m2m_db_table(), 604 'column': None, 605 'schema': schema}) 546 606 547 607 return sequence_list 548 608 -
django/db/backends/creation.py
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
a b 32 32 """ 33 33 return '%x' % (abs(hash(args)) % 4294967296L) # 2**32 34 34 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 35 46 def sql_create_model(self, model, style, known_models=set()): 36 47 """ 37 48 Returns the SQL required to create a single model, as a tuple of: … … 80 91 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ 81 92 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])) 82 93 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) + ' ('] 84 95 for i, line in enumerate(table_output): # Combine and add commas. 85 96 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 86 97 full_statement.append(')') … … 92 103 if opts.has_auto_field: 93 104 # Add any extra SQL needed to support auto-incrementing primary keys. 94 105 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) 96 109 if autoinc_sql: 97 110 for stmt in autoinc_sql: 98 111 final_output.append(stmt) … … 104 117 qn = self.connection.ops.quote_name 105 118 if field.rel.to in known_models: 106 119 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) + ' (' + \ 108 121 style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' + 109 122 self.connection.ops.deferrable_sql() 110 123 ] … … 130 143 for rel_class, f in pending_references[model]: 131 144 rel_opts = rel_class._meta 132 145 r_table = rel_opts.db_table 146 r_qname = rel_opts.qualified_name 133 147 r_col = f.column 134 148 table = opts.db_table 149 qname = opts.qualified_name 135 150 col = opts.get_field(f.rel.field_name).column 136 151 # For MySQL, r_name must be unique in the first 64 characters. 137 152 # So we are careful with character usage here. 138 153 r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table)) 139 154 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), 142 157 self.connection.ops.deferrable_sql())) 143 158 del pending_references[model] 144 159 return final_output … … 169 184 from django.db.backends.util import truncate_name 170 185 171 186 output = [] 172 if f. auto_created:187 if f.rel.through._meta.auto_created: 173 188 opts = model._meta 174 189 qn = self.connection.ops.quote_name 175 190 tablespace = f.db_tablespace or opts.db_tablespace … … 182 197 else: 183 198 tablespace_sql = '' 184 199 table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ 185 style.SQL_TABLE(qn(f.m2m_ db_table())) + ' (']200 style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' ('] 186 201 table_output.append(' %s %s %s%s,' % 187 202 (style.SQL_FIELD(qn('id')), 188 203 style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)), … … 214 229 self.connection.ops.deferrable_sql())) 215 230 216 231 # Add any extra SQL needed to support auto-incrementing PKs 217 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id') 232 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(), 233 f.m2m_db_table(), 234 'id') 218 235 if autoinc_sql: 219 236 for stmt in autoinc_sql: 220 237 output.append(stmt) … … 237 254 (style.SQL_FIELD(qn(field.m2m_column_name())), 238 255 style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)), 239 256 style.SQL_KEYWORD('NOT NULL REFERENCES'), 240 style.SQL_TABLE( qn(opts.db_table)),257 style.SQL_TABLE(opts.qualified_name), 241 258 style.SQL_FIELD(qn(opts.pk.column)), 242 259 self.connection.ops.deferrable_sql()), 243 260 ' %s %s %s %s (%s)%s,' % 244 261 (style.SQL_FIELD(qn(field.m2m_reverse_name())), 245 262 style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)), 246 263 style.SQL_KEYWORD('NOT NULL REFERENCES'), 247 style.SQL_TABLE( qn(field.rel.to._meta.db_table)),264 style.SQL_TABLE(field.rel.to._meta.qualified_name), 248 265 style.SQL_FIELD(qn(field.rel.to._meta.pk.column)), 249 266 self.connection.ops.deferrable_sql()) 250 267 ] … … 274 291 tablespace_sql = '' 275 292 else: 276 293 tablespace_sql = '' 294 index_name = '%s_%s' % (model._meta.db_table, f.column) 295 index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name) 277 296 output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' + 278 style.SQL_TABLE( qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +297 style.SQL_TABLE(index_name) + ' ' + 279 298 style.SQL_KEYWORD('ON') + ' ' + 280 style.SQL_TABLE( qn(model._meta.db_table)) + ' ' +299 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 281 300 "(%s)" % style.SQL_FIELD(qn(f.column)) + 282 301 "%s;" % tablespace_sql] 283 302 else: 284 303 output = [] 285 304 return output 286 305 306 def sql_destroy_schema(self, schema, style): 307 """" 308 Returns the SQL required to destroy a single schema. 309 """ 310 qn = self.connection.ops.quote_name 311 output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema)) 312 return output 313 287 314 def sql_destroy_model(self, model, references_to_delete, style): 288 315 "Return the DROP TABLE and restraint dropping statements for a single model" 289 316 if not model._meta.managed or model._meta.proxy: … … 291 318 # Drop the table now 292 319 qn = self.connection.ops.quote_name 293 320 output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 294 style.SQL_TABLE( qn(model._meta.db_table)))]321 style.SQL_TABLE(model._meta.qualified_name))] 295 322 if model in references_to_delete: 296 323 output.extend(self.sql_remove_table_constraints(model, references_to_delete, style)) 297 324 298 325 if model._meta.has_auto_field: 299 ds = self.connection.ops.drop_sequence_sql(model._meta.db_table) 326 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 327 model._meta.db_table) 300 328 if ds: 301 329 output.append(ds) 302 330 return output … … 310 338 qn = self.connection.ops.quote_name 311 339 for rel_class, f in references_to_delete[model]: 312 340 table = rel_class._meta.db_table 341 qname = rel_class._meta.qualified_name 313 342 col = f.column 314 343 r_table = model._meta.db_table 315 344 r_col = model._meta.get_field(f.rel.field_name).column 316 345 r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table)) 317 346 output.append('%s %s %s %s;' % \ 318 347 (style.SQL_KEYWORD('ALTER TABLE'), 319 style.SQL_TABLE(qn (table)),348 style.SQL_TABLE(qname), 320 349 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()), 321 350 style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length())))) 322 351 del references_to_delete[model] … … 332 361 333 362 qn = self.connection.ops.quote_name 334 363 output = [] 335 if f. auto_created:364 if f.rel.through._meta.auto_created: 336 365 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), 337 style.SQL_TABLE(qn(f.m2m_db_table())))) 338 ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column)) 366 style.SQL_TABLE(f.m2m_qualified_name()))) 367 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 368 "%s_%s" % (model._meta.db_table, f.column)) 339 369 if ds: 340 370 output.append(ds) 341 371 return output … … 348 378 if verbosity >= 1: 349 379 print "Creating test database '%s'..." % self.connection.alias 350 380 351 test_database_name = self._create_test_db(verbosity, autoclobber) 381 schema_apps = self._get_app_with_schemas() 382 schemas = self._get_schemas(schema_apps) 383 test_database_name = self._create_test_db(verbosity, autoclobber, schemas) 352 384 353 385 self.connection.close() 354 386 self.connection.settings_dict["NAME"] = test_database_name 355 387 can_rollback = self._rollback_works() 356 388 self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback 357 389 390 # Create the test schemas. 391 cursor = self.connection.cursor() 392 self._create_test_schemas(verbosity, schemas, cursor) 393 358 394 call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) 359 395 360 396 if settings.CACHE_BACKEND.startswith('db://'): … … 368 404 369 405 return test_database_name 370 406 371 def _create_test_db(self, verbosity, autoclobber): 407 def _create_test_schemas(self, verbosity, schemas, cursor): 408 from django.core.management.color import no_style 409 style = no_style() 410 for schema in schemas: 411 if verbosity >= 1: 412 print "Creating schema %s" % schema 413 cursor.execute(self.sql_create_schema(schema, style)) 414 415 def _destroy_test_schemas(self, verbosity, schemas, cursor): 416 from django.core.management.color import no_style 417 style = no_style() 418 for schema in schemas: 419 if verbosity >= 1: 420 print "Destroying schema %s" % schema 421 cursor.execute(self.sql_destroy_schema(schema, style)) 422 if verbosity >= 1: 423 print "Schema %s destroyed" % schema 424 425 def _get_schemas(self, apps): 426 from django.db import models 427 schemas = set() 428 for app in apps: 429 app_models = models.get_models(app) 430 for model in app_models: 431 schema = model._meta.db_schema 432 if not schema or schema in schemas: 433 continue 434 schemas.add(schema) 435 return schemas 436 437 def _get_app_with_schemas(self): 438 from django.db import models 439 apps = models.get_apps() 440 schema_apps = set() 441 for app in apps: 442 app_models = models.get_models(app) 443 for model in app_models: 444 schema = model._meta.db_schema 445 if not schema or app in schema_apps: 446 continue 447 schema_apps.add(app) 448 return schema_apps 449 450 def _create_test_db(self, verbosity, autoclobber, schemas): 372 451 "Internal implementation - creates the test db tables." 373 452 suffix = self.sql_table_creation_suffix() 374 453 … … 394 473 try: 395 474 if verbosity >= 1: 396 475 print "Destroying old test database..." 476 self._destroy_test_schemas(verbosity, schemas, cursor) 397 477 cursor.execute("DROP DATABASE %s" % qn(test_database_name)) 398 478 if verbosity >= 1: 399 479 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 170 170 return name # Quoting once is enough. 171 171 return "`%s`" % name 172 172 173 def prep_db_table(self, db_schema, db_table): 174 qn = self.quote_name 175 if db_schema: 176 return "%s.%s" % (qn(db_schema), qn(db_table)) 177 else: 178 return qn(db_table) 179 180 def prep_db_index(self, db_schema, db_index): 181 return self.prep_db_table(db_schema, db_index) 182 173 183 def random_function_sql(self): 174 184 return 'RAND()' 175 185 … … 179 189 # to clear all tables of all data 180 190 if tables: 181 191 sql = ['SET FOREIGN_KEY_CHECKS = 0;'] 182 for tablein tables:183 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self. quote_name(table))))192 for (schema, table) in tables: 193 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table)))) 184 194 sql.append('SET FOREIGN_KEY_CHECKS = 1;') 185 195 186 196 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements 187 197 # to reset sequence indices 188 sql.extend(["%s %s %s %s %s;" % \ 189 (style.SQL_KEYWORD('ALTER'), 190 style.SQL_KEYWORD('TABLE'), 191 style.SQL_TABLE(self.quote_name(sequence['table'])), 192 style.SQL_KEYWORD('AUTO_INCREMENT'), 193 style.SQL_FIELD('= 1'), 194 ) for sequence in sequences]) 198 for sequence_info in sequences: 199 schema_name = sequence_info['schema'] 200 table_name = self.prep_db_table(schema_name, sequence_info['table']) 201 sql.append("%s %s %s %s %s;" % \ 202 (style.SQL_KEYWORD('ALTER'), 203 style.SQL_KEYWORD('TABLE'), 204 style.SQL_TABLE(table_name), 205 style.SQL_KEYWORD('AUTO_INCREMENT'), 206 style.SQL_FIELD('= 1'), 207 )) 195 208 return sql 196 209 else: 197 210 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 57 57 class DatabaseOperations(BaseDatabaseOperations): 58 58 compiler_module = "django.db.backends.oracle.compiler" 59 59 60 def autoinc_sql(self, table, column):60 def autoinc_sql(self, schema, table, column): 61 61 # To simulate auto-incrementing primary keys in Oracle, we have to 62 62 # create a sequence and a trigger. 63 63 sq_name = get_sequence_name(table) 64 64 tr_name = get_trigger_name(table) 65 tbl_name = self.quote_name(table) 65 tbl_name = self.prep_db_table(schema, table) 66 sq_qname = self.prep_db_table(schema, sq_name) 67 tr_qname = self.prep_db_table(schema, tr_name) 66 68 col_name = self.quote_name(column) 67 69 sequence_sql = """ 68 70 DECLARE … … 71 73 SELECT COUNT(*) INTO i FROM USER_CATALOG 72 74 WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE'; 73 75 IF i = 0 THEN 74 EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';76 EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s'; 75 77 END IF; 76 78 END; 77 79 /""" % locals() 78 80 trigger_sql = """ 79 CREATE OR REPLACE TRIGGER "%(tr_name)s"81 CREATE OR REPLACE TRIGGER %(tr_qname)s 80 82 BEFORE INSERT ON %(tbl_name)s 81 83 FOR EACH ROW 82 84 WHEN (new.%(col_name)s IS NULL) 83 85 BEGIN 84 SELECT "%(sq_name)s".nextval86 SELECT %(sq_qname)s.nextval 85 87 INTO :new.%(col_name)s FROM dual; 86 88 END; 87 89 /""" % locals() … … 158 160 def deferrable_sql(self): 159 161 return " DEFERRABLE INITIALLY DEFERRED" 160 162 161 def drop_sequence_sql(self, table): 162 return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) 163 def drop_sequence_sql(self, schema, table): 164 sequence_name = self.prep_db_table(schema, get_sequence_name(table)) 165 return "DROP SEQUENCE %s;" % sequence_name 163 166 164 167 def fetch_returned_insert_id(self, cursor): 165 168 return long(cursor._insert_id_var.getvalue()) … … 170 173 else: 171 174 return "%s" 172 175 173 def last_insert_id(self, cursor, table_name, pk_name):174 sq_name = get_sequence_name(table_name)175 cursor.execute('SELECT "%s".currval FROM dual' % sq_name)176 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 177 sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name)) 178 cursor.execute('SELECT %s.currval FROM dual' % sq_name) 176 179 return cursor.fetchone()[0] 177 180 178 181 def lookup_cast(self, lookup_type): … … 183 186 def max_name_length(self): 184 187 return 30 185 188 189 def prep_db_table(self, db_schema, db_table): 190 qn = self.quote_name 191 if db_schema: 192 return "%s.%s" % (qn(db_schema), qn(db_table)) 193 else: 194 return qn(db_table) 195 196 def prep_db_index(self, db_schema, db_index): 197 return self.prep_db_table(db_schema, db_index) 198 186 199 def prep_for_iexact_query(self, x): 187 200 return x 188 201 … … 233 246 def sql_flush(self, style, tables, sequences): 234 247 # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 235 248 # 'TRUNCATE z;'... style SQL statements 249 sql = [] 236 250 if tables: 237 251 # Oracle does support TRUNCATE, but it seems to get us into 238 252 # FK referential trouble, whereas DELETE FROM table works. 239 sql = ['%s %s %s;' % \ 240 (style.SQL_KEYWORD('DELETE'), 241 style.SQL_KEYWORD('FROM'), 242 style.SQL_FIELD(self.quote_name(table))) 243 for table in tables] 253 for schema, table in tables: 254 table = self.prep_db_table(schema, table) 255 sql.append('%s %s %s;' % \ 256 (style.SQL_KEYWORD('DELETE'), 257 style.SQL_KEYWORD('FROM'), 258 style.SQL_FIELD(table))) 244 259 # Since we've just deleted all the rows, running our sequence 245 260 # ALTER code will reset the sequence to 0. 246 261 for sequence_info in sequences: 247 sequence_name = get_sequence_name(sequence_info['table']) 248 table_name = self.quote_name(sequence_info['table']) 262 schema_name = sequence_info['schema'] 263 sequence_name = self.prep_db_table(schema_name, 264 get_sequence_name(sequence_info['table'])) 265 table_name = self.prep_db_table(schema_name, 266 sequence_info['table']) 249 267 column_name = self.quote_name(sequence_info['column'] or 'id') 250 268 query = _get_sequence_reset_sql() % {'sequence': sequence_name, 251 269 'table': table_name, 252 270 'column': column_name} 253 271 sql.append(query) 254 return sql 255 else: 256 return [] 272 return sql 257 273 258 274 def sequence_reset_sql(self, style, model_list): 259 275 from django.db import models … … 262 278 for model in model_list: 263 279 for f in model._meta.local_fields: 264 280 if isinstance(f, models.AutoField): 265 table_name = self.quote_name(model._meta.db_table) 266 sequence_name = get_sequence_name(model._meta.db_table) 281 table_name = model._meta.qualified_name 282 sequence_name = self.prep_db_table(model._meta.db_schema, 283 get_sequence_name(model._meta.db_table)) 267 284 column_name = self.quote_name(f.column) 268 285 output.append(query % {'sequence': sequence_name, 269 286 'table': table_name, … … 273 290 break 274 291 for f in model._meta.many_to_many: 275 292 if not f.rel.through: 276 table_name = self.quote_name(f.m2m_db_table()) 277 sequence_name = get_sequence_name(f.m2m_db_table()) 293 table_name = self.quote_name(f.m2m_qualified_name()) 294 sequence_name = self.prep_db_table(f.m2m_db_schema(), 295 get_sequence_name(f.m2m_db_table())) 278 296 column_name = self.quote_name('id') 279 297 output.append(query % {'sequence': sequence_name, 280 298 'table': table_name, … … 618 636 BEGIN 619 637 LOCK TABLE %(table)s IN SHARE MODE; 620 638 SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; 621 SELECT "%(sequence)s".nextval INTO cval FROM dual;639 SELECT %(sequence)s.nextval INTO cval FROM dual; 622 640 cval := startvalue - cval; 623 641 IF cval != 0 THEN 624 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"MINVALUE 0 INCREMENT BY '||cval;625 SELECT "%(sequence)s".nextval INTO cval FROM dual;626 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"INCREMENT BY 1';642 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval; 643 SELECT %(sequence)s.nextval INTO cval FROM dual; 644 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1'; 627 645 END IF; 628 646 COMMIT; 629 647 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/creation.py
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
a b 50 50 tablespace_sql = '' 51 51 52 52 def get_index_sql(index_name, opclass=''): 53 index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name) 53 54 return (style.SQL_KEYWORD('CREATE INDEX') + ' ' + 54 style.SQL_TABLE( qn(index_name)) + ' ' +55 style.SQL_TABLE(index_name) + ' ' + 55 56 style.SQL_KEYWORD('ON') + ' ' + 56 style.SQL_TABLE( qn(db_table)) + ' ' +57 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 57 58 "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) + 58 59 "%s;" % tablespace_sql) 59 60 -
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): 57 cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) 56 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 57 sequence_name = '%s_%s_seq' % (table_name, pk_name) 58 sequence_name = self.prep_db_table(schema_name, sequence_name) 59 cursor.execute("SELECT CURRVAL('%s')" % sequence_name) 58 60 return cursor.fetchone()[0] 59 61 60 62 def no_limit_value(self): … … 65 67 return name # Quoting once is enough. 66 68 return '"%s"' % name 67 69 70 def prep_db_table(self, db_schema, db_table): 71 qn = self.quote_name 72 if db_schema: 73 return "%s.%s" % (qn(db_schema), qn(db_table)) 74 else: 75 return qn(db_table) 76 68 77 def sql_flush(self, style, tables, sequences): 69 78 if tables: 79 qnames = [self.prep_db_table(schema, table) 80 for (schema, table) in tables] 70 81 if self.postgres_version[0:2] >= (8,1): 71 82 # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* 72 83 # in order to be able to truncate tables referenced by a foreign … … 74 85 # statement. 75 86 sql = ['%s %s;' % \ 76 87 (style.SQL_KEYWORD('TRUNCATE'), 77 style.SQL_FIELD(', '.join( [self.quote_name(table) for table in tables]))88 style.SQL_FIELD(', '.join(qnames)) 78 89 )] 79 90 else: 80 91 # Older versions of Postgres can't do TRUNCATE in a single call, so … … 82 93 sql = ['%s %s %s;' % \ 83 94 (style.SQL_KEYWORD('DELETE'), 84 95 style.SQL_KEYWORD('FROM'), 85 style.SQL_FIELD( self.quote_name(table))86 ) for table in tables]96 style.SQL_FIELD(qname) 97 ) for qname in qnames] 87 98 88 99 # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements 89 100 # to reset sequence indices 90 101 for sequence_info in sequences: 102 schema_name = sequence_info['schema'] 91 103 table_name = sequence_info['table'] 92 104 column_name = sequence_info['column'] 93 105 if column_name and len(column_name) > 0: 94 106 sequence_name = '%s_%s_seq' % (table_name, column_name) 95 107 else: 96 108 sequence_name = '%s_id_seq' % table_name 109 sequence_name = self.prep_db_table(schema_name, sequence_name) 97 110 sql.append("%s setval('%s', 1, false);" % \ 98 111 (style.SQL_KEYWORD('SELECT'), 99 style.SQL_FIELD(se lf.quote_name(sequence_name)))112 style.SQL_FIELD(sequence_name)) 100 113 ) 101 114 return sql 102 115 else: … … 112 125 # if there are records (as the max pk value is already in use), otherwise set it to false. 113 126 for f in model._meta.local_fields: 114 127 if isinstance(f, models.AutoField): 128 sequence_name = qn('%s_%s_seq' % (model._meta.db_table, 129 f.column)) # XXX: generic schemas support 130 sequence_name = self.prep_db_table(model._meta.db_schema, 131 sequence_name) 115 132 output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 116 133 (style.SQL_KEYWORD('SELECT'), 117 style.SQL_FIELD( qn('%s_%s_seq' % (model._meta.db_table, f.column))),134 style.SQL_FIELD(sequence_name), 118 135 style.SQL_FIELD(qn(f.column)), 119 136 style.SQL_FIELD(qn(f.column)), 120 137 style.SQL_KEYWORD('IS NOT'), 121 138 style.SQL_KEYWORD('FROM'), 122 style.SQL_TABLE( qn(model._meta.db_table))))139 style.SQL_TABLE(model._meta.qualified_name))) 123 140 break # Only one AutoField is allowed per model, so don't bother continuing. 124 141 for f in model._meta.many_to_many: 125 142 if not f.rel.through: 143 sequence_name = qn('%s_id_seq' % f.m2m_db_table()) # XXX: generic schemas support 144 sequence_name = self.prep_db_table(f.m2m_db_schema(), 145 sequence_name) 126 146 output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 127 147 (style.SQL_KEYWORD('SELECT'), 128 style.SQL_FIELD( qn('%s_id_seq' % f.m2m_db_table())),148 style.SQL_FIELD(sequence_name), 129 149 style.SQL_FIELD(qn('id')), 130 150 style.SQL_FIELD(qn('id')), 131 151 style.SQL_KEYWORD('IS NOT'), 132 152 style.SQL_KEYWORD('FROM'), 133 style.SQL_TABLE(qn(f.m2m_ db_table()))))153 style.SQL_TABLE(qn(f.m2m_qualified_name())))) 134 154 return output 135 155 136 156 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 93 93 (style.SQL_KEYWORD('DELETE'), 94 94 style.SQL_KEYWORD('FROM'), 95 95 style.SQL_FIELD(self.quote_name(table)) 96 ) for tablein tables]96 ) for (_, table) in tables] 97 97 # Note: No requirement for reset of auto-incremented indices (cf. other 98 98 # sql_flush() implementations). Just return SQL at this point 99 99 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 833 833 if isinstance(self.rel.to, basestring): 834 834 target = self.rel.to 835 835 else: 836 target = self.rel.to._meta. db_table836 target = self.rel.to._meta.qualified_name 837 837 cls._meta.duplicate_targets[self.column] = (target, "o2m") 838 838 839 839 def contribute_to_related_class(self, cls, related): … … 922 922 to = to.lower() 923 923 meta = type('Meta', (object,), { 924 924 'db_table': field._get_m2m_db_table(klass._meta), 925 'db_schema': field._get_m2m_db_schema(klass._meta), 925 926 'managed': managed, 926 927 'auto_created': klass, 927 928 'app_label': klass._meta.app_label, … … 951 952 through=kwargs.pop('through', None)) 952 953 953 954 self.db_table = kwargs.pop('db_table', None) 955 self.db_schema = kwargs.pop('db_schema', '') 954 956 if kwargs['rel'].through is not None: 955 957 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 956 958 … … 972 974 return util.truncate_name('%s_%s' % (opts.db_table, self.name), 973 975 connection.ops.max_name_length()) 974 976 977 def _get_m2m_db_schema(self, opts): 978 "Function that can be curried to provide the m2m schema name for this relation" 979 if self.rel.through is not None and self.rel.through._meta.db_schema: 980 return self.rel.through._meta.db_schema 981 return self.db_schema 982 983 def _get_m2m_qualified_name(self, opts): 984 "Function that can be curried to provide the qualified m2m table name for this relation" 985 schema = self._get_m2m_db_schema(opts) 986 table = self._get_m2m_db_table(opts) 987 return connection.ops.prep_db_table(schema, table) 988 975 989 def _get_m2m_attr(self, related, attr): 976 990 "Function that can be curried to provide the source column name for the m2m table" 977 991 cache_attr = '_m2m_%s_cache' % attr … … 1061 1075 1062 1076 # Set up the accessor for the m2m table name for the relation 1063 1077 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 1078 self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta) 1079 self.m2m_qualified_name = curry(self._get_m2m_qualified_name, 1080 cls._meta) 1064 1081 1065 1082 # Populate some necessary rel arguments so that cross-app relations 1066 1083 # work correctly. … … 1072 1089 if isinstance(self.rel.to, basestring): 1073 1090 target = self.rel.to 1074 1091 else: 1075 target = self.rel.to._meta. db_table1092 target = self.rel.to._meta.qualified_name 1076 1093 cls._meta.duplicate_targets[self.column] = (target, "m2m") 1077 1094 1078 1095 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 21 21 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 22 22 'unique_together', 'permissions', 'get_latest_by', 23 23 'order_with_respect_to', 'app_label', 'db_tablespace', 24 'abstract', 'managed', 'proxy', 'auto_created' )24 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema') 25 25 26 26 class Options(object): 27 27 def __init__(self, meta, app_label=None): … … 30 30 self.module_name, self.verbose_name = None, None 31 31 self.verbose_name_plural = None 32 32 self.db_table = '' 33 self.db_schema = settings.DATABASE_SCHEMA 34 self.qualified_name = '' 33 35 self.ordering = [] 34 36 self.unique_together = [] 35 37 self.permissions = [] … … 104 106 self.db_table = "%s_%s" % (self.app_label, self.module_name) 105 107 self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) 106 108 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 = '' 107 115 108 116 def _prepare(self, model): 109 117 if self.order_with_respect_to: … … 177 185 self.pk = target._meta.pk 178 186 self.proxy_for_model = target 179 187 self.db_table = target._meta.db_table 188 self.db_schema = target._meta.db_schema 189 self.qualified_name = target._meta.qualified_name 180 190 181 191 def __repr__(self): 182 192 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 25 25 might not have all the pieces in place at that time. 26 26 """ 27 27 if not self.query.tables: 28 self.query.join((None, self.query.model._meta. db_table, None, None))28 self.query.join((None, self.query.model._meta.qualified_name, None, None)) 29 29 if (not self.query.select and self.query.default_cols and not 30 30 self.query.included_inherited_models): 31 31 self.query.setup_inherited_models() … … 255 255 alias = start_alias 256 256 else: 257 257 link_field = opts.get_ancestor_link(model) 258 alias = self.query.join((start_alias, model._meta. db_table,258 alias = self.query.join((start_alias, model._meta.qualified_name, 259 259 link_field.column, model._meta.pk.column)) 260 260 seen[model] = alias 261 261 else: … … 456 456 result.append('%s%s%s' % (connector, qn(name), alias_str)) 457 457 first = False 458 458 for t in self.query.extra_tables: 459 alias, unused = self.query.table_alias( t)459 alias, unused = self.query.table_alias(qn(t)) 460 460 # Only add the alias if it's not already present (the table_alias() 461 461 # calls increments the refcount, so an alias refcount of one means 462 462 # this is the only reference. … … 536 536 # what "used" specifies). 537 537 avoid = avoid_set.copy() 538 538 dupe_set = orig_dupe_set.copy() 539 table = f.rel.to._meta. db_table539 table = f.rel.to._meta.qualified_name 540 540 if nullable or f.null: 541 541 promote = True 542 542 else: … … 560 560 ()) 561 561 dupe_set.add((opts, lhs_col)) 562 562 int_opts = int_model._meta 563 alias = self.query.join((alias, int_opts. db_table, lhs_col,564 int_opts.pk.column), exclusions=used,563 alias = self.query.join((alias, int_opts.qualified_name, 564 lhs_col, int_opts.pk.column), exclusions=used, 565 565 promote=promote) 566 566 alias_chain.append(alias) 567 567 for (dupe_opts, dupe_col) in dupe_set: … … 615 615 # what "used" specifies). 616 616 avoid = avoid_set.copy() 617 617 dupe_set = orig_dupe_set.copy() 618 table = model._meta. db_table618 table = model._meta.qualified_name 619 619 620 620 int_opts = opts 621 621 alias = root_alias … … 638 638 dupe_set.add((opts, lhs_col)) 639 639 int_opts = int_model._meta 640 640 alias = self.query.join( 641 (alias, int_opts. db_table, lhs_col, int_opts.pk.column),641 (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column), 642 642 exclusions=used, promote=True, reuse=used 643 643 ) 644 644 alias_chain.append(alias) … … 779 779 # going to be column names (so we can avoid the extra overhead). 780 780 qn = self.connection.ops.quote_name 781 781 opts = self.query.model._meta 782 result = ['INSERT INTO %s' % qn(opts.db_table)]782 result = ['INSERT INTO %s' % opts.qualified_name] 783 783 result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns])) 784 784 values = [self.placeholder(*v) for v in self.query.values] 785 785 result.append('VALUES (%s)' % ', '.join(values)) 786 786 params = self.query.params 787 787 if self.return_id and self.connection.features.can_return_id_from_insert: 788 col = "%s.%s" % ( qn(opts.db_table), qn(opts.pk.column))788 col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column)) 789 789 r_fmt, r_params = self.connection.ops.return_insert_id() 790 790 result.append(r_fmt % col) 791 791 params = params + r_params … … 799 799 if self.connection.features.can_return_id_from_insert: 800 800 return self.connection.ops.fetch_returned_insert_id(cursor) 801 801 return self.connection.ops.last_insert_id(cursor, 802 self.query.model._meta.db_table, self.query.model._meta.pk.column) 802 self.query.model._meta.db_schema, self.query.model._meta.db_table, 803 self.query.model._meta.pk.column) 803 804 804 805 805 806 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 570 570 Callback used by deferred_to_columns(). The "target" parameter should 571 571 be a set instance. 572 572 """ 573 table = model._meta. db_table573 table = model._meta.qualified_name 574 574 if table not in target: 575 575 target[table] = set() 576 576 for field in fields: 577 577 target[table].add(field.column) 578 578 579 579 580 580 581 def table_alias(self, table_name, create=False): 581 582 """ 582 583 Returns a table alias for the given table_name and whether this is a … … 752 753 alias = self.tables[0] 753 754 self.ref_alias(alias) 754 755 else: 755 alias = self.join((None, self.model._meta.db_table, None, None)) 756 alias = self.join((None, self.model._meta.qualified_name, 757 None, None)) 756 758 return alias 757 759 758 760 def count_active_tables(self): … … 865 867 seen[model] = root_alias 866 868 else: 867 869 link_field = opts.get_ancestor_link(model) 868 seen[model] = self.join((root_alias, model._meta.db_table, 870 seen[model] = self.join((root_alias, 871 model._meta.qualified_name, 869 872 link_field.column, model._meta.pk.column)) 870 873 self.included_inherited_models = seen 871 874 … … 1190 1193 (id(opts), lhs_col), ())) 1191 1194 dupe_set.add((opts, lhs_col)) 1192 1195 opts = int_model._meta 1193 alias = self.join((alias, opts.db_table, lhs_col, 1194 opts.pk.column), exclusions=exclusions) 1196 alias = self.join((alias, opts.qualified_name, 1197 lhs_col, opts.pk.column), 1198 exclusions=exclusions) 1195 1199 joins.append(alias) 1196 1200 exclusions.add(alias) 1197 1201 for (dupe_opts, dupe_col) in dupe_set: … … 1216 1220 (table1, from_col1, to_col1, table2, from_col2, 1217 1221 to_col2, opts, target) = cached_data 1218 1222 else: 1219 table1 = field.m2m_ db_table()1223 table1 = field.m2m_qualified_name() 1220 1224 from_col1 = opts.pk.column 1221 1225 to_col1 = field.m2m_column_name() 1222 1226 opts = field.rel.to._meta 1223 table2 = opts. db_table1227 table2 = opts.qualified_name 1224 1228 from_col2 = field.m2m_reverse_name() 1225 1229 to_col2 = opts.pk.column 1226 1230 target = opts.pk … … 1247 1251 else: 1248 1252 opts = field.rel.to._meta 1249 1253 target = field.rel.get_related_field() 1250 table = opts. db_table1254 table = opts.qualified_name 1251 1255 from_col = field.column 1252 1256 to_col = target.column 1253 1257 orig_opts._join_cache[name] = (table, from_col, to_col, … … 1269 1273 (table1, from_col1, to_col1, table2, from_col2, 1270 1274 to_col2, opts, target) = cached_data 1271 1275 else: 1272 table1 = field.m2m_ db_table()1276 table1 = field.m2m_qualified_name() 1273 1277 from_col1 = opts.pk.column 1274 1278 to_col1 = field.m2m_reverse_name() 1275 1279 opts = orig_field.opts 1276 table2 = opts. db_table1280 table2 = opts.qualified_name 1277 1281 from_col2 = field.m2m_column_name() 1278 1282 to_col2 = opts.pk.column 1279 1283 target = opts.pk … … 1296 1300 local_field = opts.get_field_by_name( 1297 1301 field.rel.field_name)[0] 1298 1302 opts = orig_field.opts 1299 table = opts. db_table1303 table = opts.qualified_name 1300 1304 from_col = local_field.column 1301 1305 to_col = field.column 1302 1306 target = opts.pk … … 1552 1556 else: 1553 1557 opts = self.model._meta 1554 1558 if not self.select: 1555 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column), 1559 count = self.aggregates_module.Count((self.join((None, 1560 opts.qualified_name, None, None)), opts.pk.column), 1556 1561 is_summary=True, distinct=True) 1557 1562 else: 1558 1563 # 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 46 46 'in', 47 47 pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]), 48 48 AND) 49 self.do_query(related.field.m2m_ db_table(), where, using=using)49 self.do_query(related.field.m2m_qualified_name(), where, using=using) 50 50 51 51 for f in cls._meta.many_to_many: 52 52 w1 = self.where_class() … … 81 81 field = self.model._meta.pk 82 82 where.add((Constraint(None, field.column, field), 'in', 83 83 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 84 self.do_query(self.model._meta. db_table, where, using=using)84 self.do_query(self.model._meta.qualified_name, where, using=using) 85 85 86 86 class UpdateQuery(Query): 87 87 """ … … 197 197 extras.update(kwargs) 198 198 return super(InsertQuery, self).clone(klass, **extras) 199 199 200 200 201 def insert_values(self, insert_values, raw_values=False): 201 202 """ 202 203 Set up the insert query from the 'insert_values' dictionary. The -
docs/ref/models/options.txt
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
a b 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/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 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 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 55 1 56 57 # Create entry 58 >>> e = Entry(blog=b, title='Test entry') 59 >>> e.save() 60 >>> e.id 61 1 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 72 True 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 98 ok 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 3 import unittest 4 from models import * 5 6 from django.conf import settings 7 from django.db import connection, models, DEFAULT_DB_ALIAS 8 9 class SampleTests(unittest.TestCase): 10 fixtures = ['testschema',] 11 VALID_ENGINES = ['postgresql_psycopg2', 'postgresql', 'mysql','oracle'] 12 VALID_ENGINES = ['django.db.backends.%s' %b for b in VALID_ENGINES] 13 14 def test_meta_information(self): 15 e = Entry.objects.get(id=1) 16 b = Blog.objects.get(id=1) 17 if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in self.VALID_ENGINES: 18 self.assertEqual('test_schema', e._meta.db_schema) 19 if settings.DATABASE_SCHEMA: 20 self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema) 21 else: 22 self.assertFalse(b._meta.db_schema) 23 else: 24 self.assertFalse(e._meta.db_schema) # No model schema 25 self.assertFalse(b._meta.db_schema) # No global schema 26 27 def test_schema_in_m2m_declaring_model(self): 28 e = Entry.objects.get(id=1) 29 c = Category(name='Test') 30 c.save() 31 e.categories = [c] 32 33 categories= e.categories.filter(name='Test') 34 35 a = len(categories) 36 self.assertEqual(1, len(categories)) -
tests/regressiontests/introspection/tests.py
diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
a b 63 63 64 64 def test_sequence_list(self): 65 65 sequences = connection.introspection.sequence_list() 66 expected = {'table': Reporter._meta.db_table, 'column': 'id' }66 expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''} 67 67 self.assert_(expected in sequences, 68 68 'Reporter sequence not found in sequence_list()') 69 69