Ticket #6148: 6148-generic-schema-support-r11951.diff
File 6148-generic-schema-support-r11951.diff, 73.9 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 130 130 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 131 131 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 132 132 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 133 DATABASE_SCHEMA = '' # Set to empty string for default. 133 134 134 135 # The email backend to use. For possible shortcuts see django.core.mail. 135 136 # The default is to use the SMTP backend. -
django/conf/project_template/settings.py
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
a b 15 15 DATABASE_PASSWORD = '' # Not used with sqlite3. 16 16 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 17 17 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 18 DATABASE_SCHEMA = '' # Set to empty string for default. 18 19 19 20 # Local time zone for this installation. Choices can be found here: 20 21 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -
django/contrib/contenttypes/generic.py
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
a b 129 129 return self.object_id_field_name 130 130 131 131 def m2m_reverse_name(self): 132 return self.model._meta.pk.column 132 return self.rel.to._meta.pk.column 133 134 def m2m_db_schema(self): 135 return self.rel.to._meta.db_schema 136 137 def m2m_qualified_name(self): 138 schema = self.m2m_db_schema() 139 table = self.m2m_db_table() 140 return connection.ops.prep_db_table(schema, table) 133 141 134 142 def contribute_to_class(self, cls, name): 135 143 super(GenericRelation, self).contribute_to_class(cls, name) -
django/core/management/commands/syncdb.py
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
a b 49 49 cursor = connection.cursor() 50 50 51 51 # Get a list of already installed *models* so that references work right. 52 tables = connection.introspection.table_names()52 tables = [('', tn) for tn in connection.introspection.table_names()] 53 53 seen_models = connection.introspection.installed_models(tables) 54 seen_schemas = set() 54 55 created_models = set() 55 56 pending_references = {} 56 57 … … 59 60 app_name = app.__name__.split('.')[-2] 60 61 model_list = models.get_models(app, include_auto_created=True) 61 62 for model in model_list: 62 # Create the model's database table, if it doesn't already exist. 63 # Add model-defined schema tables if any. 64 db_schema = model._meta.db_schema 65 if db_schema: 66 db_schema = connection.introspection.schema_name_converter(db_schema) 67 if db_schema not in seen_schemas: 68 tables += [(db_schema, tn) for tn in 69 connection.introspection.schema_table_names(db_schema)] 70 seen_schemas.add(db_schema) 71 72 # Create the model's database table, 73 # if it doesn't already exist. 63 74 if verbosity >= 2: 64 75 print "Processing %s.%s model" % (app_name, model._meta.object_name) 65 76 opts = model._meta 66 if (connection.introspection.table_name_converter(opts.db_table) in tables or 67 (opts.auto_created and 68 connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)): 77 schema_table = (db_schema, connection.introspection.table_name_converter(opts.db_table)) 78 if schema_table in tables or \ 79 (opts.auto_created and \ 80 (db_schema, 81 connection.introspection.table_name_converter(opts.auto_created._meta.db_table)) 82 in tables): 69 83 continue 70 84 sql, references = connection.creation.sql_create_model(model, self.style, seen_models) 71 85 seen_models.add(model) … … 79 93 print "Creating table %s" % model._meta.db_table 80 94 for statement in sql: 81 95 cursor.execute(statement) 82 tables.append( connection.introspection.table_name_converter(model._meta.db_table))96 tables.append(schema_table) 83 97 84 98 85 99 transaction.commit_unless_managed() -
django/core/management/sql.py
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
a b 68 68 69 69 # Figure out which tables already exist 70 70 if cursor: 71 table_names = connection.introspection.get_table_list(cursor) 71 table_names = [('', tn) for tn in 72 connection.introspection.get_table_list(cursor)] 72 73 else: 73 74 table_names = [] 74 75 … … 79 80 80 81 references_to_delete = {} 81 82 app_models = models.get_models(app, include_auto_created=True) 83 seen_schemas = set() 82 84 for model in app_models: 83 if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names: 85 db_schema = model._meta.db_schema 86 # Find additional tables in model-defined schemas. 87 if db_schema: 88 db_schema = connection.introspection.schema_name_converter(db_schema) 89 if db_schema not in seen_schemas: 90 table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)] 91 seen_schemas.add(db_schema) 92 schema_table = (db_schema, 93 connection.introspection.table_name_converter(model._meta.db_table)) 94 95 if cursor and schema_table in table_names: 84 96 # The table exists, so it needs to be dropped 85 97 opts = model._meta 86 98 for f in opts.local_fields: … … 90 102 to_delete.add(model) 91 103 92 104 for model in app_models: 93 if connection.introspection.table_name_converter(model._meta.db_table) in table_names: 105 db_schema = model._meta.db_schema 106 if db_schema: 107 db_schema = connection.introspection.schema_name_converter(db_schema) 108 schema_table = (db_schema, 109 connection.introspection.table_name_converter(model._meta.db_table)) 110 if schema_table in table_names: 94 111 output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style)) 95 112 96 113 # Close database connection explicitly, in case this output is being piped … … 116 133 if only_django: 117 134 tables = connection.introspection.django_table_names(only_existing=True) 118 135 else: 119 tables = connection.introspection.table_names()136 tables = [('', tn) for tn in connection.introspection.table_names()] 120 137 statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list()) 121 138 return statements 122 139 -
django/db/backends/__init__.py
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
a b 109 109 a backend performs ordering or calculates the ID of a recently-inserted 110 110 row. 111 111 """ 112 def autoinc_sql(self, table, column):112 def autoinc_sql(self, schema, table, column): 113 113 """ 114 114 Returns any SQL needed to support auto-incrementing primary keys, or 115 115 None if no SQL is necessary. … … 155 155 """ 156 156 return "DROP CONSTRAINT" 157 157 158 def drop_sequence_sql(self, table):158 def drop_sequence_sql(self, schema, table): 159 159 """ 160 160 Returns any SQL necessary to drop the sequence for the given table. 161 161 Returns None if no SQL is necessary. … … 216 216 217 217 return smart_unicode(sql) % u_params 218 218 219 def last_insert_id(self, cursor, table_name, pk_name):219 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 220 220 """ 221 221 Given a cursor object that has just performed an INSERT statement into 222 222 a table that has an auto-incrementing ID, returns the newly created ID. … … 287 287 """ 288 288 raise NotImplementedError() 289 289 290 def prep_db_table(self, db_schema, db_table): 291 """ 292 Prepares and formats the table name if necessary. 293 Just returns quoted db_table if not supported. 294 """ 295 return self.quote_name(db_table) 296 297 def prep_db_index(self, db_schema, db_index): 298 """ 299 Prepares and formats the table index name if necessary. 300 Just returns quoted db_index if not supported. 301 """ 302 return self.quote_name(db_index) 303 290 304 def random_function_sql(self): 291 305 """ 292 306 Returns a SQL expression that returns a random value. … … 486 500 return name 487 501 488 502 def table_names(self): 489 "Returns a list of names of all tables that exist in the d atabase."503 "Returns a list of names of all tables that exist in the default schema." 490 504 cursor = self.connection.cursor() 491 505 return self.get_table_list(cursor) 492 506 507 def schema_name_converter(self, name): 508 """Apply a conversion to the name for the purposes of comparison. 509 510 The default schema name converter is for case sensitive comparison. 511 """ 512 return name 513 514 def get_schema_list(self, cursor): 515 "Returns a list of schemas that exist in the database" 516 return [] 517 518 def get_schema_table_list(self, cursor, schema): 519 "Returns a list of tables in a specific schema" 520 return [] 521 522 def schema_names(self): 523 cursor = self.connection.cursor() 524 return self.get_schema_list(cursor) 525 526 def schema_table_names(self, schema): 527 "Returns a list of names of all tables that exist in the database schema." 528 cursor = self.connection.cursor() 529 return self.get_schema_table_list(cursor, schema) 530 493 531 def django_table_names(self, only_existing=False): 494 532 """ 495 Returns a list of all table names that have associated Django models and496 are in INSTALLED_APPS.533 Returns a list of tuples containing all schema and table names that 534 have associated Django models and are in INSTALLED_APPS. 497 535 498 If only_existing is True, the resulting list will only include the tables499 t hat actually exist in the database.536 If only_existing is True, the resulting list will only include the 537 tables that actually exist in the database. 500 538 """ 501 539 from django.db import models 502 540 tables = set() 503 for app in models.get_apps():504 for model in models.get_models(app):505 if not model._meta.managed:506 continue507 tables.add(model._meta.db_table)508 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])509 541 if only_existing: 510 tables = [t for t in tables if self.table_name_converter(t) in self.table_names()] 542 existing_tables = set([('', tn) for tn in self.table_names()]) 543 seen_schemas = set() 544 for model in models.get_models(): 545 if not model._meta.managed: 546 continue 547 db_schema = model._meta.db_schema 548 db_table = model._meta.db_table 549 if only_existing and db_schema and db_schema not in seen_schemas: 550 existing_tables.update([(db_schema, tn) for tn in 551 self.schema_table_names(db_schema)]) 552 seen_schemas.add(db_schema) 553 tables.add((model._meta.db_schema, model._meta.db_table)) 554 for f in model._meta.local_many_to_many: 555 m2m_schema = f.m2m_db_schema() 556 m2m_table = f.m2m_db_table() 557 if only_existing and m2m_schema and m2m_schema not in seen_schemas: 558 existing_tables.update([(m2m_schema, tn) for tn in 559 self.schema_table_names(m2m_schema)]) 560 seen_schemas.add(m2m_schema) 561 tables.add((m2m_schema, m2m_table)) 562 if only_existing: 563 tables = [(s, t) for (s, t) in tables 564 if (self.schema_name_converter(s), 565 self.table_name_converter(t)) in existing_tables] 511 566 return tables 512 567 513 568 def installed_models(self, tables): … … 534 589 continue 535 590 for f in model._meta.local_fields: 536 591 if isinstance(f, models.AutoField): 537 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 592 sequence_list.append({'table': model._meta.db_table, 593 'column': f.column, 594 'schema': model._meta.db_schema}) 538 595 break # Only one AutoField is allowed per model, so don't bother continuing. 539 596 540 597 for f in model._meta.local_many_to_many: 598 schema = f.m2m_db_schema() 541 599 # If this is an m2m using an intermediate table, 542 600 # we don't need to reset the sequence. 543 601 if f.rel.through is None: 544 sequence_list.append({'table': f.m2m_db_table(), 'column': None}) 602 sequence_list.append({'table': f.m2m_db_table(), 603 'column': None, 604 'schema': schema}) 545 605 546 606 return sequence_list 547 607 -
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 … … 157 172 from django.db.backends.util import truncate_name 158 173 159 174 output = [] 160 if f. creates_table:175 if f.rel.through._meta.auto_created: 161 176 opts = model._meta 162 177 qn = self.connection.ops.quote_name 163 178 tablespace = f.db_tablespace or opts.db_tablespace … … 170 185 else: 171 186 tablespace_sql = '' 172 187 table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ 173 style.SQL_TABLE(qn(f.m2m_ db_table())) + ' (']188 style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' ('] 174 189 table_output.append(' %s %s %s%s,' % 175 190 (style.SQL_FIELD(qn('id')), 176 191 style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()), … … 202 217 self.connection.ops.deferrable_sql())) 203 218 204 219 # Add any extra SQL needed to support auto-incrementing PKs 205 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id') 220 autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(), 221 f.m2m_db_table(), 222 'id') 206 223 if autoinc_sql: 207 224 for stmt in autoinc_sql: 208 225 output.append(stmt) … … 219 236 (style.SQL_FIELD(qn(field.m2m_column_name())), 220 237 style.SQL_COLTYPE(models.ForeignKey(model).db_type()), 221 238 style.SQL_KEYWORD('NOT NULL REFERENCES'), 222 style.SQL_TABLE( qn(opts.db_table)),239 style.SQL_TABLE(opts.qualified_name), 223 240 style.SQL_FIELD(qn(opts.pk.column)), 224 241 self.connection.ops.deferrable_sql()), 225 242 ' %s %s %s %s (%s)%s,' % 226 243 (style.SQL_FIELD(qn(field.m2m_reverse_name())), 227 244 style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type()), 228 245 style.SQL_KEYWORD('NOT NULL REFERENCES'), 229 style.SQL_TABLE( qn(field.rel.to._meta.db_table)),246 style.SQL_TABLE(field.rel.to._meta.qualified_name), 230 247 style.SQL_FIELD(qn(field.rel.to._meta.pk.column)), 231 248 self.connection.ops.deferrable_sql()) 232 249 ] … … 256 273 tablespace_sql = '' 257 274 else: 258 275 tablespace_sql = '' 276 index_name = '%s_%s' % (model._meta.db_table, f.column) 277 index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name) 259 278 output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' + 260 style.SQL_TABLE( qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +279 style.SQL_TABLE(index_name) + ' ' + 261 280 style.SQL_KEYWORD('ON') + ' ' + 262 style.SQL_TABLE( qn(model._meta.db_table)) + ' ' +281 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 263 282 "(%s)" % style.SQL_FIELD(qn(f.column)) + 264 283 "%s;" % tablespace_sql] 265 284 else: 266 285 output = [] 267 286 return output 268 287 288 def sql_destroy_schema(self, schema, style): 289 """" 290 Returns the SQL required to destroy a single schema. 291 """ 292 qn = self.connection.ops.quote_name 293 output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema)) 294 return output 295 269 296 def sql_destroy_model(self, model, references_to_delete, style): 270 297 "Return the DROP TABLE and restraint dropping statements for a single model" 271 298 if not model._meta.managed or model._meta.proxy: … … 273 300 # Drop the table now 274 301 qn = self.connection.ops.quote_name 275 302 output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 276 style.SQL_TABLE( qn(model._meta.db_table)))]303 style.SQL_TABLE(model._meta.qualified_name))] 277 304 if model in references_to_delete: 278 305 output.extend(self.sql_remove_table_constraints(model, references_to_delete, style)) 279 306 280 307 if model._meta.has_auto_field: 281 ds = self.connection.ops.drop_sequence_sql(model._meta.db_table) 308 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 309 model._meta.db_table) 282 310 if ds: 283 311 output.append(ds) 284 312 return output … … 292 320 qn = self.connection.ops.quote_name 293 321 for rel_class, f in references_to_delete[model]: 294 322 table = rel_class._meta.db_table 323 qname = rel_class._meta.qualified_name 295 324 col = f.column 296 325 r_table = model._meta.db_table 297 326 r_col = model._meta.get_field(f.rel.field_name).column 298 327 r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table)) 299 328 output.append('%s %s %s %s;' % \ 300 329 (style.SQL_KEYWORD('ALTER TABLE'), 301 style.SQL_TABLE(qn (table)),330 style.SQL_TABLE(qname), 302 331 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()), 303 332 style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length())))) 304 333 del references_to_delete[model] … … 308 337 "Returns the DROP TABLE statements for a single m2m field" 309 338 qn = self.connection.ops.quote_name 310 339 output = [] 311 if f. creates_table:340 if f.rel.through._meta.auto_created: 312 341 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), 313 style.SQL_TABLE(qn(f.m2m_db_table())))) 314 ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column)) 342 style.SQL_TABLE(f.m2m_qualified_name()))) 343 ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema, 344 "%s_%s" % (model._meta.db_table, f.column)) 315 345 if ds: 316 346 output.append(ds) 317 347 return output … … 324 354 if verbosity >= 1: 325 355 print "Creating test database..." 326 356 327 test_database_name = self._create_test_db(verbosity, autoclobber) 357 schema_apps = self._get_app_with_schemas() 358 schemas = self._get_schemas(schema_apps) 359 test_database_name = self._create_test_db(verbosity, autoclobber, schemas) 328 360 329 361 self.connection.close() 330 362 settings.DATABASE_NAME = test_database_name … … 333 365 settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback 334 366 self.connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback 335 367 368 # Create the test schemas. 369 cursor = self.connection.cursor() 370 self._create_test_schemas(verbosity, schemas, cursor) 371 336 372 call_command('syncdb', verbosity=verbosity, interactive=False) 337 373 338 374 if settings.CACHE_BACKEND.startswith('db://'): … … 346 382 347 383 return test_database_name 348 384 349 def _create_test_db(self, verbosity, autoclobber): 385 def _create_test_schemas(self, verbosity, schemas, cursor): 386 from django.core.management.color import no_style 387 style = no_style() 388 for schema in schemas: 389 if verbosity >= 1: 390 print "Creating schema %s" % schema 391 cursor.execute(self.sql_create_schema(schema, style)) 392 393 def _destroy_test_schemas(self, verbosity, schemas, cursor): 394 from django.core.management.color import no_style 395 style = no_style() 396 for schema in schemas: 397 if verbosity >= 1: 398 print "Destroying schema %s" % schema 399 cursor.execute(self.sql_destroy_schema(schema, style)) 400 if verbosity >= 1: 401 print "Schema %s destroyed" % schema 402 403 def _get_schemas(self, apps): 404 from django.db import models 405 schemas = set() 406 for app in apps: 407 app_models = models.get_models(app) 408 for model in app_models: 409 schema = model._meta.db_schema 410 if not schema or schema in schemas: 411 continue 412 schemas.add(schema) 413 return schemas 414 415 def _get_app_with_schemas(self): 416 from django.db import models 417 apps = models.get_apps() 418 schema_apps = set() 419 for app in apps: 420 app_models = models.get_models(app) 421 for model in app_models: 422 schema = model._meta.db_schema 423 if not schema or app in schema_apps: 424 continue 425 schema_apps.add(app) 426 return schema_apps 427 428 def _create_test_db(self, verbosity, autoclobber, schemas): 350 429 "Internal implementation - creates the test db tables." 351 430 suffix = self.sql_table_creation_suffix() 352 431 … … 372 451 try: 373 452 if verbosity >= 1: 374 453 print "Destroying old test database..." 454 self._destroy_test_schemas(verbosity, schemas, cursor) 375 455 cursor.execute("DROP DATABASE %s" % qn(test_database_name)) 376 456 if verbosity >= 1: 377 457 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 160 160 return name # Quoting once is enough. 161 161 return "`%s`" % name 162 162 163 def prep_db_table(self, db_schema, db_table): 164 qn = self.quote_name 165 if db_schema: 166 return "%s.%s" % (qn(db_schema), qn(db_table)) 167 else: 168 return qn(db_table) 169 170 def prep_db_index(self, db_schema, db_index): 171 return self.prep_db_table(db_schema, db_index) 172 163 173 def random_function_sql(self): 164 174 return 'RAND()' 165 175 … … 169 179 # to clear all tables of all data 170 180 if tables: 171 181 sql = ['SET FOREIGN_KEY_CHECKS = 0;'] 172 for tablein tables:173 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self. quote_name(table))))182 for (schema, table) in tables: 183 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table)))) 174 184 sql.append('SET FOREIGN_KEY_CHECKS = 1;') 175 185 176 186 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements 177 187 # to reset sequence indices 178 sql.extend(["%s %s %s %s %s;" % \ 179 (style.SQL_KEYWORD('ALTER'), 180 style.SQL_KEYWORD('TABLE'), 181 style.SQL_TABLE(self.quote_name(sequence['table'])), 182 style.SQL_KEYWORD('AUTO_INCREMENT'), 183 style.SQL_FIELD('= 1'), 184 ) for sequence in sequences]) 188 for sequence_info in sequences: 189 schema_name = sequence_info['schema'] 190 table_name = self.prep_db_table(schema_name, sequence_info['table']) 191 sql.append("%s %s %s %s %s;" % \ 192 (style.SQL_KEYWORD('ALTER'), 193 style.SQL_KEYWORD('TABLE'), 194 style.SQL_TABLE(table_name), 195 style.SQL_KEYWORD('AUTO_INCREMENT'), 196 style.SQL_FIELD('= 1'), 197 )) 185 198 return sql 186 199 else: 187 200 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 41 41 def sql_for_inline_foreign_key_references(self, field, known_models, style): 42 42 "All inline references are pending under MySQL" 43 43 return [], True 44 44 45 45 def sql_for_inline_many_to_many_references(self, model, field, style): 46 46 from django.db import models 47 47 opts = model._meta 48 48 qn = self.connection.ops.quote_name 49 49 50 50 table_output = [ 51 51 ' %s %s %s,' % 52 52 (style.SQL_FIELD(qn(field.m2m_column_name())), … … 64 64 field.rel.to._meta.db_table, field.rel.to._meta.pk.column) 65 65 ] 66 66 return table_output, deferred 67 68 No newline at end of file 67 68 def default_schema(self): 69 return settings.DATABASE_NAME 70 71 def sql_create_schema(self, schema, style): 72 """ 73 Returns the SQL required to create a single schema. 74 In MySQL schemas are synonymous to databases 75 """ 76 qn = self.connection.ops.quote_name 77 output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema)) 78 return output 79 80 def sql_destroy_schema(self, schema, style): 81 """" 82 Returns the SQL required to destroy a single schema. 83 """ 84 qn = self.connection.ops.quote_name 85 output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema)) 86 return output -
django/db/backends/mysql/introspection.py
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
a b 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 56 56 class DatabaseOperations(BaseDatabaseOperations): 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() … … 108 110 def deferrable_sql(self): 109 111 return " DEFERRABLE INITIALLY DEFERRED" 110 112 111 def drop_sequence_sql(self, table): 112 return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) 113 def drop_sequence_sql(self, schema, table): 114 sequence_name = self.prep_db_table(schema, get_sequence_name(table)) 115 return "DROP SEQUENCE %s;" % sequence_name 113 116 114 117 def fetch_returned_insert_id(self, cursor): 115 118 return long(cursor._insert_id_var.getvalue()) … … 120 123 else: 121 124 return "%s" 122 125 123 def last_insert_id(self, cursor, table_name, pk_name):124 sq_name = get_sequence_name(table_name)125 cursor.execute('SELECT "%s".currval FROM dual' % sq_name)126 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 127 sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name)) 128 cursor.execute('SELECT %s.currval FROM dual' % sq_name) 126 129 return cursor.fetchone()[0] 127 130 128 131 def lookup_cast(self, lookup_type): … … 133 136 def max_name_length(self): 134 137 return 30 135 138 139 def prep_db_table(self, db_schema, db_table): 140 qn = self.quote_name 141 if db_schema: 142 return "%s.%s" % (qn(db_schema), qn(db_table)) 143 else: 144 return qn(db_table) 145 146 def prep_db_index(self, db_schema, db_index): 147 return self.prep_db_table(db_schema, db_index) 148 136 149 def prep_for_iexact_query(self, x): 137 150 return x 138 151 … … 186 199 def sql_flush(self, style, tables, sequences): 187 200 # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 188 201 # 'TRUNCATE z;'... style SQL statements 202 sql = [] 189 203 if tables: 190 204 # Oracle does support TRUNCATE, but it seems to get us into 191 205 # FK referential trouble, whereas DELETE FROM table works. 192 sql = ['%s %s %s;' % \ 193 (style.SQL_KEYWORD('DELETE'), 194 style.SQL_KEYWORD('FROM'), 195 style.SQL_FIELD(self.quote_name(table))) 196 for table in tables] 206 for schema, table in tables: 207 table = self.prep_db_table(schema, table) 208 sql.append('%s %s %s;' % \ 209 (style.SQL_KEYWORD('DELETE'), 210 style.SQL_KEYWORD('FROM'), 211 style.SQL_FIELD(table))) 197 212 # Since we've just deleted all the rows, running our sequence 198 213 # ALTER code will reset the sequence to 0. 199 214 for sequence_info in sequences: 200 sequence_name = get_sequence_name(sequence_info['table']) 201 table_name = self.quote_name(sequence_info['table']) 215 schema_name = sequence_info['schema'] 216 sequence_name = self.prep_db_table(schema_name, 217 get_sequence_name(sequence_info['table'])) 218 table_name = self.prep_db_table(schema_name, 219 sequence_info['table']) 202 220 column_name = self.quote_name(sequence_info['column'] or 'id') 203 221 query = _get_sequence_reset_sql() % {'sequence': sequence_name, 204 222 'table': table_name, 205 223 'column': column_name} 206 224 sql.append(query) 207 return sql 208 else: 209 return [] 225 return sql 210 226 211 227 def sequence_reset_sql(self, style, model_list): 212 228 from django.db import models … … 215 231 for model in model_list: 216 232 for f in model._meta.local_fields: 217 233 if isinstance(f, models.AutoField): 218 table_name = self.quote_name(model._meta.db_table) 219 sequence_name = get_sequence_name(model._meta.db_table) 234 table_name = model._meta.qualified_name 235 sequence_name = self.prep_db_table(model._meta.db_schema, 236 get_sequence_name(model._meta.db_table)) 220 237 column_name = self.quote_name(f.column) 221 238 output.append(query % {'sequence': sequence_name, 222 239 'table': table_name, … … 226 243 break 227 244 for f in model._meta.many_to_many: 228 245 if not f.rel.through: 229 table_name = self.quote_name(f.m2m_db_table()) 230 sequence_name = get_sequence_name(f.m2m_db_table()) 246 table_name = self.quote_name(f.m2m_qualified_name()) 247 sequence_name = self.prep_db_table(f.m2m_db_schema(), 248 get_sequence_name(f.m2m_db_table())) 231 249 column_name = self.quote_name('id') 232 250 output.append(query % {'sequence': sequence_name, 233 251 'table': table_name, … … 551 569 BEGIN 552 570 LOCK TABLE %(table)s IN SHARE MODE; 553 571 SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; 554 SELECT "%(sequence)s".nextval INTO cval FROM dual;572 SELECT %(sequence)s.nextval INTO cval FROM dual; 555 573 cval := startvalue - cval; 556 574 IF cval != 0 THEN 557 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"MINVALUE 0 INCREMENT BY '||cval;558 SELECT "%(sequence)s".nextval INTO cval FROM dual;559 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"INCREMENT BY 1';575 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval; 576 SELECT %(sequence)s.nextval INTO cval FROM dual; 577 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1'; 560 578 END IF; 561 579 COMMIT; 562 580 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 40 40 'URLField': 'VARCHAR2(%(max_length)s)', 41 41 } 42 42 43 def sql_create_schema(self, schema, style, password=None, 44 tablespace=None, temp_tablespace=None): 45 qn = self.connection.ops.quote_name 46 lock_account = (password is None) 47 if lock_account: 48 password = schema 49 output = [] 50 output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'), 51 qn(schema), 52 style.SQL_KEYWORD('IDENTIFIED BY'), 53 qn(password))) 54 if tablespace: 55 output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'), 56 qn(tablespace))) 57 if temp_tablespace: 58 output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'), 59 qn(temp_tablespace))) 60 if lock_account: 61 output.append(style.SQL_KEYWORD('ACCOUNT LOCK')) 62 return '\n'.join(output) 63 64 def sql_destroy_schema(self, schema, style): 65 qn = self.connection.ops.quote_name 66 return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema), 67 style.SQL_KEYWORD('CASCADE')) 68 43 69 remember = {} 44 70 45 def _create_test_db(self, verbosity=1, autoclobber=False ):71 def _create_test_db(self, verbosity=1, autoclobber=False, schema): 46 72 TEST_DATABASE_NAME = self._test_database_name(settings) 47 73 TEST_DATABASE_USER = self._test_database_user(settings) 48 74 TEST_DATABASE_PASSWD = self._test_database_passwd(settings) … … 175 201 DEFAULT TABLESPACE %(tblspace)s 176 202 TEMPORARY TABLESPACE %(tblspace_temp)s 177 203 """, 178 """GRANT CONNECT, RESOURCE TO %(user)s""",204 """GRANT CONNECT, RESOURCE, CREATE USER, DROP USER, CREATE ANY TABLE, ALTER ANY TABLE, CREATE ANY INDEX, CREATE ANY SEQUENCE, CREATE ANY TRIGGER, SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE TO %(user)s""", 179 205 ] 180 206 self._execute_statements(cursor, statements, parameters, verbosity) 181 207 -
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 51 51 tablespace_sql = '' 52 52 53 53 def get_index_sql(index_name, opclass=''): 54 index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name) 54 55 return (style.SQL_KEYWORD('CREATE INDEX') + ' ' + 55 style.SQL_TABLE( qn(index_name)) + ' ' +56 style.SQL_TABLE(index_name) + ' ' + 56 57 style.SQL_KEYWORD('ON') + ' ' + 57 style.SQL_TABLE( qn(db_table)) + ' ' +58 style.SQL_TABLE(model._meta.qualified_name) + ' ' + 58 59 "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) + 59 60 "%s;" % tablespace_sql) 60 61 -
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 52 52 return 'HOST(%s)' 53 53 return '%s' 54 54 55 def last_insert_id(self, cursor, table_name, pk_name): 56 cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) 55 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 56 sequence_name = '%s_%s_seq' % (table_name, pk_name) 57 sequence_name = self.prep_db_table(schema_name, sequence_name) 58 cursor.execute("SELECT CURRVAL('%s')" % sequence_name) 57 59 return cursor.fetchone()[0] 58 60 59 61 def no_limit_value(self): … … 64 66 return name # Quoting once is enough. 65 67 return '"%s"' % name 66 68 69 def prep_db_table(self, db_schema, db_table): 70 qn = self.quote_name 71 if db_schema: 72 return "%s.%s" % (qn(db_schema), qn(db_table)) 73 else: 74 return qn(db_table) 75 67 76 def sql_flush(self, style, tables, sequences): 68 77 if tables: 78 qnames = [self.prep_db_table(schema, table) 79 for (schema, table) in tables] 69 80 if self.postgres_version[0:2] >= (8,1): 70 81 # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* 71 82 # in order to be able to truncate tables referenced by a foreign … … 73 84 # statement. 74 85 sql = ['%s %s;' % \ 75 86 (style.SQL_KEYWORD('TRUNCATE'), 76 style.SQL_FIELD(', '.join( [self.quote_name(table) for table in tables]))87 style.SQL_FIELD(', '.join(qnames)) 77 88 )] 78 89 else: 79 90 # Older versions of Postgres can't do TRUNCATE in a single call, so … … 81 92 sql = ['%s %s %s;' % \ 82 93 (style.SQL_KEYWORD('DELETE'), 83 94 style.SQL_KEYWORD('FROM'), 84 style.SQL_FIELD( self.quote_name(table))85 ) for table in tables]95 style.SQL_FIELD(qname) 96 ) for qname in qnames] 86 97 87 98 # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements 88 99 # to reset sequence indices 89 100 for sequence_info in sequences: 101 schema_name = sequence_info['schema'] 90 102 table_name = sequence_info['table'] 91 103 column_name = sequence_info['column'] 92 104 if column_name and len(column_name) > 0: 93 105 sequence_name = '%s_%s_seq' % (table_name, column_name) 94 106 else: 95 107 sequence_name = '%s_id_seq' % table_name 108 sequence_name = self.prep_db_table(schema_name, sequence_name) 96 109 sql.append("%s setval('%s', 1, false);" % \ 97 110 (style.SQL_KEYWORD('SELECT'), 98 style.SQL_FIELD(se lf.quote_name(sequence_name)))111 style.SQL_FIELD(sequence_name)) 99 112 ) 100 113 return sql 101 114 else: … … 111 124 # if there are records (as the max pk value is already in use), otherwise set it to false. 112 125 for f in model._meta.local_fields: 113 126 if isinstance(f, models.AutoField): 127 sequence_name = qn('%s_%s_seq' % (model._meta.db_table, 128 f.column)) # XXX: generic schemas support 129 sequence_name = self.prep_db_table(model._meta.db_schema, 130 sequence_name) 114 131 output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 115 132 (style.SQL_KEYWORD('SELECT'), 116 style.SQL_FIELD( qn('%s_%s_seq' % (model._meta.db_table, f.column))),133 style.SQL_FIELD(sequence_name), 117 134 style.SQL_FIELD(qn(f.column)), 118 135 style.SQL_FIELD(qn(f.column)), 119 136 style.SQL_KEYWORD('IS NOT'), 120 137 style.SQL_KEYWORD('FROM'), 121 style.SQL_TABLE( qn(model._meta.db_table))))138 style.SQL_TABLE(model._meta.qualified_name))) 122 139 break # Only one AutoField is allowed per model, so don't bother continuing. 123 140 for f in model._meta.many_to_many: 124 141 if not f.rel.through: 142 sequence_name = qn('%s_id_seq' % f.m2m_db_table()) # XXX: generic schemas support 143 sequence_name = self.prep_db_table(f.m2m_db_schema(), 144 sequence_name) 125 145 output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ 126 146 (style.SQL_KEYWORD('SELECT'), 127 style.SQL_FIELD( qn('%s_id_seq' % f.m2m_db_table())),147 style.SQL_FIELD(sequence_name), 128 148 style.SQL_FIELD(qn('id')), 129 149 style.SQL_FIELD(qn('id')), 130 150 style.SQL_KEYWORD('IS NOT'), 131 151 style.SQL_KEYWORD('FROM'), 132 style.SQL_TABLE(qn(f.m2m_ db_table()))))152 style.SQL_TABLE(qn(f.m2m_qualified_name())))) 133 153 return output 134 154 135 155 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 94 94 (style.SQL_KEYWORD('DELETE'), 95 95 style.SQL_KEYWORD('FROM'), 96 96 style.SQL_FIELD(self.quote_name(table)) 97 ) for tablein tables]97 ) for (_, table) in tables] 98 98 # Note: No requirement for reset of auto-incremented indices (cf. other 99 99 # sql_flush() implementations). Just return SQL at this point 100 100 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 30 30 'TextField': 'text', 31 31 'TimeField': 'time', 32 32 } 33 33 34 34 def sql_for_pending_references(self, model, style, pending_references): 35 35 "SQLite3 doesn't support constraints" 36 36 return [] … … 38 38 def sql_remove_table_constraints(self, model, references_to_delete, style): 39 39 "SQLite3 doesn't support constraints" 40 40 return [] 41 42 def _create_test_db(self, verbosity, autoclobber ):41 42 def _create_test_db(self, verbosity, autoclobber, schemas): 43 43 if settings.TEST_DATABASE_NAME and settings.TEST_DATABASE_NAME != ":memory:": 44 44 test_database_name = settings.TEST_DATABASE_NAME 45 45 # Erase the old test database … … 64 64 else: 65 65 test_database_name = ":memory:" 66 66 return test_database_name 67 67 68 68 def _destroy_test_db(self, test_database_name, verbosity): 69 69 if test_database_name and test_database_name != ":memory:": 70 70 # Remove the SQLite database file -
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py
a b 611 611 # into a pure queryset operation. 612 612 where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \ 613 613 (qn('_order'), op, qn('_order'), 614 qn(self._meta.db_table), qn(self._meta.pk.column))]614 self._meta.qualified_name, qn(self._meta.pk.column))] 615 615 params = [self.pk] 616 616 obj = self._default_manager.filter(**{order_field.name: getattr(self, order_field.attname)}).extra(where=where, params=params).order_by(order)[:1].get() 617 617 setattr(self, cachename, obj) -
django/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
a b 752 752 if isinstance(self.rel.to, basestring): 753 753 target = self.rel.to 754 754 else: 755 target = self.rel.to._meta. db_table755 target = self.rel.to._meta.qualified_name 756 756 cls._meta.duplicate_targets[self.column] = (target, "o2m") 757 757 758 758 def contribute_to_related_class(self, cls, related): … … 835 835 to = to.lower() 836 836 meta = type('Meta', (object,), { 837 837 'db_table': field._get_m2m_db_table(klass._meta), 838 'db_schema': field._get_m2m_db_schema(klass._meta), 838 839 'managed': managed, 839 840 'auto_created': klass, 840 841 'app_label': klass._meta.app_label, … … 864 865 through=kwargs.pop('through', None)) 865 866 866 867 self.db_table = kwargs.pop('db_table', None) 868 self.db_schema = kwargs.pop('db_schema', '') 867 869 if kwargs['rel'].through is not None: 868 870 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 869 871 … … 885 887 return util.truncate_name('%s_%s' % (opts.db_table, self.name), 886 888 connection.ops.max_name_length()) 887 889 890 def _get_m2m_db_schema(self, opts): 891 "Function that can be curried to provide the m2m schema name for this relation" 892 if self.rel.through is not None and self.rel.through._meta.db_schema: 893 return self.rel.through._meta.db_schema 894 return self.db_schema 895 896 def _get_m2m_qualified_name(self, opts): 897 "Function that can be curried to provide the qualified m2m table name for this relation" 898 schema = self._get_m2m_db_schema(opts) 899 table = self._get_m2m_db_table(opts) 900 return connection.ops.prep_db_table(schema, table) 901 888 902 def _get_m2m_attr(self, related, attr): 889 903 "Function that can be curried to provide the source column name for the m2m table" 890 904 cache_attr = '_m2m_%s_cache' % attr … … 974 988 975 989 # Set up the accessor for the m2m table name for the relation 976 990 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 991 self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta) 992 self.m2m_qualified_name = curry(self._get_m2m_qualified_name, 993 cls._meta) 977 994 978 995 # Populate some necessary rel arguments so that cross-app relations 979 996 # work correctly. … … 985 1002 if isinstance(self.rel.to, basestring): 986 1003 target = self.rel.to 987 1004 else: 988 target = self.rel.to._meta. db_table1005 target = self.rel.to._meta.qualified_name 989 1006 cls._meta.duplicate_targets[self.column] = (target, "m2m") 990 1007 991 1008 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/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
a b 620 620 might not have all the pieces in place at that time. 621 621 """ 622 622 if not self.tables: 623 self.join((None, self.model._meta. db_table, None, None))623 self.join((None, self.model._meta.qualified_name, None, None)) 624 624 if (not self.select and self.default_cols and not 625 625 self.included_inherited_models): 626 626 self.setup_inherited_models() … … 720 720 Callback used by deferred_to_columns(). The "target" parameter should 721 721 be a set instance. 722 722 """ 723 table = model._meta. db_table723 table = model._meta.qualified_name 724 724 if table not in target: 725 725 target[table] = set() 726 726 for field in fields: … … 837 837 alias = start_alias 838 838 else: 839 839 link_field = opts.get_ancestor_link(model) 840 alias = self.join((start_alias, model._meta.db_table, 840 alias = self.join((start_alias, 841 model._meta.qualified_name, 841 842 link_field.column, model._meta.pk.column)) 842 843 seen[model] = alias 843 844 else: … … 900 901 result.append('%s%s%s' % (connector, qn(name), alias_str)) 901 902 first = False 902 903 for t in self.extra_tables: 903 alias, unused = self.table_alias( t)904 alias, unused = self.table_alias(qn(t)) 904 905 # Only add the alias if it's not already present (the table_alias() 905 906 # calls increments the refcount, so an alias refcount of one means 906 907 # this is the only reference. … … 1245 1246 alias = self.tables[0] 1246 1247 self.ref_alias(alias) 1247 1248 else: 1248 alias = self.join((None, self.model._meta.db_table, None, None)) 1249 alias = self.join((None, self.model._meta.qualified_name, 1250 None, None)) 1249 1251 return alias 1250 1252 1251 1253 def count_active_tables(self): … … 1358 1360 seen[model] = root_alias 1359 1361 else: 1360 1362 link_field = opts.get_ancestor_link(model) 1361 seen[model] = self.join((root_alias, model._meta.db_table, 1363 seen[model] = self.join((root_alias, 1364 model._meta.qualified_name, 1362 1365 link_field.column, model._meta.pk.column)) 1363 1366 self.included_inherited_models = seen 1364 1367 … … 1416 1419 # what "used" specifies). 1417 1420 avoid = avoid_set.copy() 1418 1421 dupe_set = orig_dupe_set.copy() 1419 table = f.rel.to._meta. db_table1422 table = f.rel.to._meta.qualified_name 1420 1423 if nullable or f.null: 1421 1424 promote = True 1422 1425 else: … … 1440 1443 ()) 1441 1444 dupe_set.add((opts, lhs_col)) 1442 1445 int_opts = int_model._meta 1443 alias = self.join((alias, int_opts. db_table, lhs_col,1444 int_opts.pk.column), exclusions=used,1446 alias = self.join((alias, int_opts.qualified_name, 1447 lhs_col, int_opts.pk.column), exclusions=used, 1445 1448 promote=promote) 1446 1449 alias_chain.append(alias) 1447 1450 for (dupe_opts, dupe_col) in dupe_set: … … 1795 1798 (id(opts), lhs_col), ())) 1796 1799 dupe_set.add((opts, lhs_col)) 1797 1800 opts = int_model._meta 1798 alias = self.join((alias, opts.db_table, lhs_col, 1799 opts.pk.column), exclusions=exclusions) 1801 alias = self.join((alias, opts.qualified_name, 1802 lhs_col, opts.pk.column), 1803 exclusions=exclusions) 1800 1804 joins.append(alias) 1801 1805 exclusions.add(alias) 1802 1806 for (dupe_opts, dupe_col) in dupe_set: … … 1821 1825 (table1, from_col1, to_col1, table2, from_col2, 1822 1826 to_col2, opts, target) = cached_data 1823 1827 else: 1824 table1 = field.m2m_ db_table()1828 table1 = field.m2m_qualified_name() 1825 1829 from_col1 = opts.pk.column 1826 1830 to_col1 = field.m2m_column_name() 1827 1831 opts = field.rel.to._meta 1828 table2 = opts. db_table1832 table2 = opts.qualified_name 1829 1833 from_col2 = field.m2m_reverse_name() 1830 1834 to_col2 = opts.pk.column 1831 1835 target = opts.pk … … 1852 1856 else: 1853 1857 opts = field.rel.to._meta 1854 1858 target = field.rel.get_related_field() 1855 table = opts. db_table1859 table = opts.qualified_name 1856 1860 from_col = field.column 1857 1861 to_col = target.column 1858 1862 orig_opts._join_cache[name] = (table, from_col, to_col, … … 1874 1878 (table1, from_col1, to_col1, table2, from_col2, 1875 1879 to_col2, opts, target) = cached_data 1876 1880 else: 1877 table1 = field.m2m_ db_table()1881 table1 = field.m2m_qualified_name() 1878 1882 from_col1 = opts.pk.column 1879 1883 to_col1 = field.m2m_reverse_name() 1880 1884 opts = orig_field.opts 1881 table2 = opts. db_table1885 table2 = opts.qualified_name 1882 1886 from_col2 = field.m2m_column_name() 1883 1887 to_col2 = opts.pk.column 1884 1888 target = opts.pk … … 1901 1905 local_field = opts.get_field_by_name( 1902 1906 field.rel.field_name)[0] 1903 1907 opts = orig_field.opts 1904 table = opts. db_table1908 table = opts.qualified_name 1905 1909 from_col = local_field.column 1906 1910 to_col = field.column 1907 1911 target = opts.pk … … 2140 2144 self.group_by = [] 2141 2145 if self.connection.features.allows_group_by_pk: 2142 2146 if len(self.select) == len(self.model._meta.fields): 2143 self.group_by.append((self.model._meta. db_table,2147 self.group_by.append((self.model._meta.qualified_name, 2144 2148 self.model._meta.pk.column)) 2145 2149 return 2146 2150 … … 2162 2166 else: 2163 2167 opts = self.model._meta 2164 2168 if not self.select: 2165 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column), 2169 count = self.aggregates_module.Count((self.join((None, 2170 opts.qualified_name, None, None)), opts.pk.column), 2166 2171 is_summary=True, distinct=True) 2167 2172 else: 2168 2173 # 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 54 54 'in', 55 55 pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]), 56 56 AND) 57 self.do_query(related.field.m2m_ db_table(), where)57 self.do_query(related.field.m2m_qualified_name(), where) 58 58 59 59 for f in cls._meta.many_to_many: 60 60 w1 = self.where_class() … … 85 85 field = self.model._meta.pk 86 86 where.add((Constraint(None, field.column, field), 'in', 87 87 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 88 self.do_query(self.model._meta. db_table, where)88 self.do_query(self.model._meta.qualified_name, where) 89 89 90 90 class UpdateQuery(Query): 91 91 """ … … 304 304 # going to be column names (so we can avoid the extra overhead). 305 305 qn = self.connection.ops.quote_name 306 306 opts = self.model._meta 307 result = ['INSERT INTO %s' % qn(opts.db_table)]307 result = ['INSERT INTO %s' % opts.qualified_name] 308 308 result.append('(%s)' % ', '.join([qn(c) for c in self.columns])) 309 309 result.append('VALUES (%s)' % ', '.join(self.values)) 310 310 params = self.params 311 311 if self.return_id and self.connection.features.can_return_id_from_insert: 312 col = "%s.%s" % ( qn(opts.db_table), qn(opts.pk.column))312 col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column)) 313 313 r_fmt, r_params = self.connection.ops.return_insert_id() 314 314 result.append(r_fmt % col) 315 315 params = params + r_params … … 323 323 if self.connection.features.can_return_id_from_insert: 324 324 return self.connection.ops.fetch_returned_insert_id(cursor) 325 325 return self.connection.ops.last_insert_id(cursor, 326 self.model._meta.db_table, self.model._meta.pk.column) 326 self.model._meta.db_schema, self.model._meta.db_table, 327 self.model._meta.pk.column) 327 328 328 329 def insert_values(self, insert_values, raw_values=False): 329 330 """ -
docs/ref/models/options.txt
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
a b 61 61 aren't allowed in Python variable names -- notably, the hyphen -- that's OK. 62 62 Django quotes column and table names behind the scenes. 63 63 64 .. _db_schema: 65 66 ``db_schema`` 67 ------------- 68 69 .. attribute:: Options.db_schema 70 71 .. versionadded:: 1.3 72 73 The name of the database schema to use for the model. If the backend 74 doesn't support multiple schemas, this option is ignored. 75 76 If this is used then Django will prefix the model table names with the schema 77 name. For example with MySQL Django would use ``db_schema + '.' + db_table``. 78 Be aware that PostgreSQL supports different schemas within the database. 79 MySQL solves the same thing by treating it as just another database. 80 64 81 ``db_tablespace`` 65 82 ----------------- 66 83 -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
a b 272 272 The port to use when connecting to the database. An empty string means the 273 273 default port. Not used with SQLite. 274 274 275 .. setting:: DATABASE_SCHEMA 276 277 DATABASE_SCHEMA 278 --------------- 279 280 .. versionadded:: 1.3 281 282 Default: ``''`` (Empty string) 283 284 The name of the database schema to use for models. If the backend 285 doesn't support multiple schemas, this option is ignored. An empty 286 string means the default schema. 287 288 If this is used then Django will prefix any table names with the schema name. 289 The schema can be overriden on a per-model basis, for details see 290 :ref:`db_schema`. 291 275 292 .. setting:: DATABASE_USER 276 293 277 294 DATABASE_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 8 9 class SampleTests(unittest.TestCase): 10 fixtures = ['testschema',] 11 VALID_ENGINES=['postgresql_psycopg2', 'postgresql', 'mysql','oracle'] 12 13 def test_meta_information(self): 14 e=Entry.objects.get(id=1) 15 b = Blog.objects.get(id=1) 16 if settings.DATABASE_ENGINE in self.VALID_ENGINES: 17 self.assertEqual('test_schema', e._meta.db_schema) 18 if settings.DATABASE_SCHEMA: 19 self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema) 20 else: 21 self.assertFalse(b._meta.db_schema) 22 else: 23 self.assertFalse(e._meta.db_schema) #no model schema 24 self.assertFalse(b._meta.db_schema) #no global schema 25 26 def test_schema_in_m2m_declaring_model(self): 27 e = Entry.objects.get(id=1) 28 c = Category(name='Test') 29 c.save() 30 e.categories = [c] 31 32 categories= e.categories.filter(name='Test') 33 34 a = len(categories) 35 self.assertEqual(1, len(categories)) -
tests/regressiontests/introspection/tests.py
diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
a b 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