Ticket #6148: 6148-r12948.diff
File 6148-r12948.diff, 76.2 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 140 140 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 141 141 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 142 142 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 143 DATABASE_SCHEMA = '' # Set to empty string for default. 143 144 144 145 # New format 145 146 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 8 8 from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS 9 9 from django.utils.importlib import import_module 10 10 11 def log(s): 12 l = open('/home/ramiro/django/t6148/salida.sql', 'a') 13 try: 14 l.write(s) 15 finally: 16 l.close() 11 17 12 18 class Command(NoArgsCommand): 13 19 option_list = NoArgsCommand.option_list + ( … … 50 56 connection = connections[db] 51 57 cursor = connection.cursor() 52 58 59 #log('-- ===== DB: %s\n' % connection.settings_dict['NAME']) 53 60 # Get a list of already installed *models* so that references work right. 54 tables = connection.introspection.table_names() 61 schemas = connection.introspection.schema_names() 62 if schemas: 63 tables = [] 64 default_schema_name = connection.features.default_schema_name 65 for schema in connection.introspection.schema_names(): 66 if default_schema_name and schema == default_schema_name: 67 sn = '' 68 else: 69 sn = schema 70 for tn in connection.introspection.schema_table_names(schema): 71 tables.append((sn, tn)) 72 else: 73 tables = [('', tn) for tn in connection.introspection.table_names()] 74 #tablesx = [x for x in tables] 75 #tablesx.sort() 76 #log('-- Creating tables list (%d): %s\n' % (len(tables), tablesx)) 55 77 seen_models = connection.introspection.installed_models(tables) 78 seen_schemas = set() 56 79 created_models = set() 57 80 pending_references = {} 58 81 … … 63 86 if router.allow_syncdb(db, m)]) 64 87 for app in models.get_apps() 65 88 ) 89 90 def model_schema(model): 91 db_schema = model._meta.db_schema 92 if db_schema: 93 db_schema = connection.introspection.table_name_converter(db_schema) 94 return db_schema 95 66 96 def model_installed(model): 67 97 opts = model._meta 68 98 converter = connection.introspection.table_name_converter 69 return not ((converter(opts.db_table) in tables) or 70 (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables)) 99 db_schema = model_schema(model) 100 schema_table = (db_schema, converter(opts.db_table)) 101 return not ((schema_table in tables) or 102 (opts.auto_created and \ 103 (db_schema, converter(opts.auto_created._meta.db_table)) in tables) 104 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables) 105 ) 71 106 72 107 manifest = dict( 73 108 (app_name, filter(model_installed, model_list)) … … 76 111 77 112 # Create the tables for each model 78 113 for app_name, model_list in manifest.items(): 114 #log('-- app: %s\n' % app_name) 79 115 for model in model_list: 116 # Add model-defined schema tables if any. 117 db_schema = model_schema(model) 118 #log('-- (schemas) db_schema: %s\n' % db_schema) 119 if db_schema and db_schema not in seen_schemas: 120 #log('-- (schemas) appending to tables list: %s\n' % [(db_schema, tn) for tn in connection.introspection.schema_table_names(db_schema)]) 121 tables += [(db_schema, tn) for tn in 122 connection.introspection.schema_table_names(db_schema)] 123 seen_schemas.add(db_schema) 124 80 125 # Create the model's database table, if it doesn't already exist. 81 126 if verbosity >= 2: 82 127 print "Processing %s.%s model" % (app_name, model._meta.object_name) … … 89 134 sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references)) 90 135 sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references)) 91 136 if verbosity >= 1 and sql: 92 print "Creating table %s" % model._meta.db_table 137 if db_schema: 138 print "Creating table %s.%s" % (db_schema, model._meta.db_table) 139 else: 140 print "Creating table %s" % model._meta.db_table 141 #log('%s\n' % ''.join(sql)) 93 142 for statement in sql: 94 143 cursor.execute(statement) 95 tables.append(connection.introspection.table_name_converter(model._meta.db_table)) 144 if sql: 145 #log('-- appending to tables list: %s\n' % str((db_schema, connection.introspection.table_name_converter(model._meta.db_table)))) 146 tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table))) 147 #tablesx2 = [x for x in tables] 148 #tablesx2.sort() 149 #log('-- now (%d): %s\n\n' % (len(tables), tablesx2)) 96 150 97 151 98 152 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 96 96 # integer primary keys. 97 97 related_fields_match_type = False 98 98 allow_sliced_subqueries = True 99 default_schema_name = '' 99 100 100 101 class BaseDatabaseOperations(object): 101 102 """ … … 108 109 def __init__(self): 109 110 self._cache = {} 110 111 111 def autoinc_sql(self, table, column):112 def autoinc_sql(self, schema, table, column): 112 113 """ 113 114 Returns any SQL needed to support auto-incrementing primary keys, or 114 115 None if no SQL is necessary. … … 154 155 """ 155 156 return "DROP CONSTRAINT" 156 157 157 def drop_sequence_sql(self, table):158 def drop_sequence_sql(self, schema, table): 158 159 """ 159 160 Returns any SQL necessary to drop the sequence for the given table. 160 161 Returns None if no SQL is necessary. … … 215 216 216 217 return smart_unicode(sql) % u_params 217 218 218 def last_insert_id(self, cursor, table_name, pk_name):219 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 219 220 """ 220 221 Given a cursor object that has just performed an INSERT statement into 221 222 a table that has an auto-incrementing ID, returns the newly created ID. … … 289 290 """ 290 291 raise NotImplementedError() 291 292 293 def prep_db_table(self, db_schema, db_table): 294 """ 295 Prepares and formats the table name if necessary. 296 Just returns quoted db_table if not supported. 297 """ 298 return self.quote_name(db_table) 299 300 def prep_db_index(self, db_schema, db_index): 301 """ 302 Prepares and formats the table index name if necessary. 303 Just returns quoted db_index if not supported. 304 """ 305 return self.quote_name(db_index) 306 292 307 def random_function_sql(self): 293 308 """ 294 309 Returns a SQL expression that returns a random value. … … 488 503 return name 489 504 490 505 def table_names(self): 491 "Returns a list of names of all tables that exist in the d atabase."506 "Returns a list of names of all tables that exist in the default schema." 492 507 cursor = self.connection.cursor() 493 508 return self.get_table_list(cursor) 494 509 510 def schema_name_converter(self, name): 511 """Apply a conversion to the name for the purposes of comparison. 512 513 The default schema name converter is for case sensitive comparison. 514 """ 515 return name 516 517 def get_schema_list(self, cursor): 518 "Returns a list of schemas that exist in the database" 519 return [] 520 521 def get_schema_table_list(self, cursor, schema): 522 "Returns a list of tables in a specific schema" 523 return [] 524 525 def schema_names(self): 526 cursor = self.connection.cursor() 527 return self.get_schema_list(cursor) 528 529 def schema_table_names(self, schema): 530 "Returns a list of names of all tables that exist in the database schema." 531 cursor = self.connection.cursor() 532 return self.get_schema_table_list(cursor, schema) 533 495 534 def django_table_names(self, only_existing=False): 496 535 """ 497 Returns a list of all table names that have associated Django models and498 are in INSTALLED_APPS.536 Returns a list of tuples containing all schema and table names that 537 have associated Django models and are in INSTALLED_APPS. 499 538 500 If only_existing is True, the resulting list will only include the tables501 t hat actually exist in the database.539 If only_existing is True, the resulting list will only include the 540 tables that actually exist in the database. 502 541 """ 503 542 from django.db import models, router 504 543 tables = set() 505 for app in models.get_apps():506 for model in models.get_models(app):507 if not model._meta.managed:508 continue509 if not router.allow_syncdb(self.connection.alias, model):510 continue511 tables.add(model._meta.db_table)512 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])513 544 if only_existing: 514 tables = [t for t in tables if self.table_name_converter(t) in self.table_names()] 545 existing_tables = set([('', tn) for tn in self.table_names()]) 546 seen_schemas = set() 547 for model in models.get_models(): 548 if not model._meta.managed: 549 continue 550 if not router.allow_syncdb(self.connection.alias, model): 551 continue 552 db_schema = model._meta.db_schema 553 if only_existing and db_schema and db_schema not in seen_schemas: 554 existing_tables.update([(db_schema, tn) for tn in 555 self.schema_table_names(db_schema)]) 556 seen_schemas.add(db_schema) 557 tables.add((db_schema, model._meta.db_table)) 558 m2m_tables = [] 559 for f in model._meta.local_many_to_many: 560 m2m_schema = f.m2m_db_schema() 561 m2m_table = f.m2m_db_table() 562 if only_existing and m2m_schema and m2m_schema not in seen_schemas: 563 existing_tables.update([(m2m_schema, tn) for tn in 564 self.schema_table_names(m2m_schema)]) 565 seen_schemas.add(m2m_schema) 566 m2m_tables.append((m2m_schema, m2m_table)) 567 tables.update(m2m_tables) 568 if only_existing: 569 tables = [(s, t) for (s, t) in tables 570 if (self.schema_name_converter(s), 571 self.table_name_converter(t)) in existing_tables] 515 572 return tables 516 573 517 574 def installed_models(self, tables): … … 541 598 continue 542 599 for f in model._meta.local_fields: 543 600 if isinstance(f, models.AutoField): 544 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 601 sequence_list.append({'table': model._meta.db_table, 602 'column': f.column, 603 'schema': model._meta.db_schema}) 545 604 break # Only one AutoField is allowed per model, so don't bother continuing. 546 605 547 606 for f in model._meta.local_many_to_many: 607 schema = f.m2m_db_schema() 548 608 # If this is an m2m using an intermediate table, 549 609 # we don't need to reset the sequence. 550 610 if f.rel.through is None: 551 sequence_list.append({'table': f.m2m_db_table(), 'column': None}) 611 sequence_list.append({'table': f.m2m_db_table(), 612 'column': None, 613 'schema': schema}) 552 614 553 615 return sequence_list 554 616 -
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(qn(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 173 173 return name # Quoting once is enough. 174 174 return "`%s`" % name 175 175 176 def prep_db_table(self, db_schema, db_table): 177 qn = self.quote_name 178 if db_schema: 179 return "%s.%s" % (qn(db_schema), qn(db_table)) 180 else: 181 return qn(db_table) 182 183 def prep_db_index(self, db_schema, db_index): 184 return self.prep_db_table(db_schema, db_index) 185 176 186 def random_function_sql(self): 177 187 return 'RAND()' 178 188 … … 182 192 # to clear all tables of all data 183 193 if tables: 184 194 sql = ['SET FOREIGN_KEY_CHECKS = 0;'] 185 for tablein tables:186 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self. quote_name(table))))195 for (schema, table) in tables: 196 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table)))) 187 197 sql.append('SET FOREIGN_KEY_CHECKS = 1;') 188 198 189 199 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements 190 200 # to reset sequence indices 191 sql.extend(["%s %s %s %s %s;" % \ 192 (style.SQL_KEYWORD('ALTER'), 193 style.SQL_KEYWORD('TABLE'), 194 style.SQL_TABLE(self.quote_name(sequence['table'])), 195 style.SQL_KEYWORD('AUTO_INCREMENT'), 196 style.SQL_FIELD('= 1'), 197 ) for sequence in sequences]) 201 for sequence_info in sequences: 202 schema_name = sequence_info['schema'] 203 table_name = self.prep_db_table(schema_name, sequence_info['table']) 204 sql.append("%s %s %s %s %s;" % \ 205 (style.SQL_KEYWORD('ALTER'), 206 style.SQL_KEYWORD('TABLE'), 207 style.SQL_TABLE(table_name), 208 style.SQL_KEYWORD('AUTO_INCREMENT'), 209 style.SQL_FIELD('= 1'), 210 )) 198 211 return sql 199 212 else: 200 213 return [] -
django/db/backends/mysql/creation.py
diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
a b 63 63 field.rel.to._meta.db_table, field.rel.to._meta.pk.column) 64 64 ] 65 65 return table_output, deferred 66 67 def default_schema(self): 68 return settings.DATABASE_NAME 69 70 def sql_create_schema(self, schema, style): 71 """ 72 Returns the SQL required to create a single schema. 73 In MySQL schemas are synonymous to databases 74 """ 75 qn = self.connection.ops.quote_name 76 output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema)) 77 return output 78 79 def sql_destroy_schema(self, schema, style): 80 """" 81 Returns the SQL required to destroy a single schema. 82 """ 83 qn = self.connection.ops.quote_name 84 output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema)) 85 return output -
django/db/backends/mysql/introspection.py
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
a b 33 33 cursor.execute("SHOW TABLES") 34 34 return [row[0] for row in cursor.fetchall()] 35 35 36 def get_schema_list(self, cursor): 37 cursor.execute("SHOW SCHEMAS") 38 return [row[0] for row in cursor.fetchall()] 39 40 def get_schema_table_list(self, cursor, schema): 41 cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema)) 42 return [schema + "." + row[0] for row in cursor.fetchall()] 43 36 44 def get_table_description(self, cursor, table_name): 37 45 "Returns a description of the table, with the DB-API cursor.description interface." 38 46 cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) -
django/db/backends/oracle/base.py
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
a b 58 58 class DatabaseOperations(BaseDatabaseOperations): 59 59 compiler_module = "django.db.backends.oracle.compiler" 60 60 61 def autoinc_sql(self, table, column):61 def autoinc_sql(self, schema, table, column): 62 62 # To simulate auto-incrementing primary keys in Oracle, we have to 63 63 # create a sequence and a trigger. 64 64 sq_name = get_sequence_name(table) 65 65 tr_name = get_trigger_name(table) 66 tbl_name = self.quote_name(table) 66 tbl_name = self.prep_db_table(schema, table) 67 sq_qname = self.prep_db_table(schema, sq_name) 68 tr_qname = self.prep_db_table(schema, tr_name) 67 69 col_name = self.quote_name(column) 68 70 sequence_sql = """ 69 71 DECLARE … … 72 74 SELECT COUNT(*) INTO i FROM USER_CATALOG 73 75 WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE'; 74 76 IF i = 0 THEN 75 EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';77 EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s'; 76 78 END IF; 77 79 END; 78 80 /""" % locals() 79 81 trigger_sql = """ 80 CREATE OR REPLACE TRIGGER "%(tr_name)s"82 CREATE OR REPLACE TRIGGER %(tr_qname)s 81 83 BEFORE INSERT ON %(tbl_name)s 82 84 FOR EACH ROW 83 85 WHEN (new.%(col_name)s IS NULL) 84 86 BEGIN 85 SELECT "%(sq_name)s".nextval87 SELECT %(sq_qname)s.nextval 86 88 INTO :new.%(col_name)s FROM dual; 87 89 END; 88 90 /""" % locals() … … 159 161 def deferrable_sql(self): 160 162 return " DEFERRABLE INITIALLY DEFERRED" 161 163 162 def drop_sequence_sql(self, table): 163 return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) 164 def drop_sequence_sql(self, schema, table): 165 sequence_name = self.prep_db_table(schema, get_sequence_name(table)) 166 return "DROP SEQUENCE %s;" % sequence_name 164 167 165 168 def fetch_returned_insert_id(self, cursor): 166 169 return long(cursor._insert_id_var.getvalue()) … … 171 174 else: 172 175 return "%s" 173 176 174 def last_insert_id(self, cursor, table_name, pk_name):175 sq_name = get_sequence_name(table_name)176 cursor.execute('SELECT "%s".currval FROM dual' % sq_name)177 def last_insert_id(self, cursor, schema_name, table_name, pk_name): 178 sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name)) 179 cursor.execute('SELECT %s.currval FROM dual' % sq_name) 177 180 return cursor.fetchone()[0] 178 181 179 182 def lookup_cast(self, lookup_type): … … 184 187 def max_name_length(self): 185 188 return 30 186 189 190 def prep_db_table(self, db_schema, db_table): 191 qn = self.quote_name 192 if db_schema: 193 return "%s.%s" % (qn(db_schema), qn(db_table)) 194 else: 195 return qn(db_table) 196 197 def prep_db_index(self, db_schema, db_index): 198 return self.prep_db_table(db_schema, db_index) 199 187 200 def prep_for_iexact_query(self, x): 188 201 return x 189 202 … … 234 247 def sql_flush(self, style, tables, sequences): 235 248 # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 236 249 # 'TRUNCATE z;'... style SQL statements 250 sql = [] 237 251 if tables: 238 252 # Oracle does support TRUNCATE, but it seems to get us into 239 253 # FK referential trouble, whereas DELETE FROM table works. 240 sql = ['%s %s %s;' % \ 241 (style.SQL_KEYWORD('DELETE'), 242 style.SQL_KEYWORD('FROM'), 243 style.SQL_FIELD(self.quote_name(table))) 244 for table in tables] 254 for schema, table in tables: 255 table = self.prep_db_table(schema, table) 256 sql.append('%s %s %s;' % \ 257 (style.SQL_KEYWORD('DELETE'), 258 style.SQL_KEYWORD('FROM'), 259 style.SQL_FIELD(table))) 245 260 # Since we've just deleted all the rows, running our sequence 246 261 # ALTER code will reset the sequence to 0. 247 262 for sequence_info in sequences: 248 sequence_name = get_sequence_name(sequence_info['table']) 249 table_name = self.quote_name(sequence_info['table']) 263 schema_name = sequence_info['schema'] 264 sequence_name = self.prep_db_table(schema_name, 265 get_sequence_name(sequence_info['table'])) 266 table_name = self.prep_db_table(schema_name, 267 sequence_info['table']) 250 268 column_name = self.quote_name(sequence_info['column'] or 'id') 251 269 query = _get_sequence_reset_sql() % {'sequence': sequence_name, 252 270 'table': table_name, 253 271 'column': column_name} 254 272 sql.append(query) 255 return sql 256 else: 257 return [] 273 return sql 258 274 259 275 def sequence_reset_sql(self, style, model_list): 260 276 from django.db import models … … 263 279 for model in model_list: 264 280 for f in model._meta.local_fields: 265 281 if isinstance(f, models.AutoField): 266 table_name = self.quote_name(model._meta.db_table) 267 sequence_name = get_sequence_name(model._meta.db_table) 282 table_name = model._meta.qualified_name 283 sequence_name = self.prep_db_table(model._meta.db_schema, 284 get_sequence_name(model._meta.db_table)) 268 285 column_name = self.quote_name(f.column) 269 286 output.append(query % {'sequence': sequence_name, 270 287 'table': table_name, … … 274 291 break 275 292 for f in model._meta.many_to_many: 276 293 if not f.rel.through: 277 table_name = self.quote_name(f.m2m_db_table()) 278 sequence_name = get_sequence_name(f.m2m_db_table()) 294 table_name = self.quote_name(f.m2m_qualified_name()) 295 sequence_name = self.prep_db_table(f.m2m_db_schema(), 296 get_sequence_name(f.m2m_db_table())) 279 297 column_name = self.quote_name('id') 280 298 output.append(query % {'sequence': sequence_name, 281 299 'table': table_name, … … 619 637 BEGIN 620 638 LOCK TABLE %(table)s IN SHARE MODE; 621 639 SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; 622 SELECT "%(sequence)s".nextval INTO cval FROM dual;640 SELECT %(sequence)s.nextval INTO cval FROM dual; 623 641 cval := startvalue - cval; 624 642 IF cval != 0 THEN 625 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"MINVALUE 0 INCREMENT BY '||cval;626 SELECT "%(sequence)s".nextval INTO cval FROM dual;627 EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s"INCREMENT BY 1';643 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval; 644 SELECT %(sequence)s.nextval INTO cval FROM dual; 645 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1'; 628 646 END IF; 629 647 COMMIT; 630 648 END; -
django/db/backends/oracle/creation.py
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
a b 39 39 'URLField': 'VARCHAR2(%(max_length)s)', 40 40 } 41 41 42 def sql_create_schema(self, schema, style, password=None, 43 tablespace=None, temp_tablespace=None): 44 qn = self.connection.ops.quote_name 45 lock_account = (password is None) 46 if lock_account: 47 password = schema 48 output = [] 49 output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'), 50 qn(schema), 51 style.SQL_KEYWORD('IDENTIFIED BY'), 52 qn(password))) 53 if tablespace: 54 output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'), 55 qn(tablespace))) 56 if temp_tablespace: 57 output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'), 58 qn(temp_tablespace))) 59 if lock_account: 60 output.append(style.SQL_KEYWORD('ACCOUNT LOCK')) 61 return '\n'.join(output) 62 63 def sql_destroy_schema(self, schema, style): 64 qn = self.connection.ops.quote_name 65 return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema), 66 style.SQL_KEYWORD('CASCADE')) 67 42 68 remember = {} 43 69 44 def _create_test_db(self, verbosity=1, autoclobber=False ):70 def _create_test_db(self, verbosity=1, autoclobber=False, schema): 45 71 TEST_NAME = self._test_database_name() 46 72 TEST_USER = self._test_database_user() 47 73 TEST_PASSWD = self._test_database_passwd() … … 174 200 DEFAULT TABLESPACE %(tblspace)s 175 201 TEMPORARY TABLESPACE %(tblspace_temp)s 176 202 """, 177 """GRANT CONNECT, RESOURCE TO %(user)s""",203 """GRANT CONNECT, RESOURCE, CREATE USER, DROP USER, CREATE ANY TABLE, ALTER ANY TABLE, CREATE ANY INDEX, CREATE ANY SEQUENCE, CREATE ANY TRIGGER, SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE TO %(user)s""", 178 204 ] 179 205 self._execute_statements(cursor, statements, parameters, verbosity) 180 206 -
django/db/backends/oracle/introspection.py
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
a b 119 119 for row in cursor.fetchall(): 120 120 indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]} 121 121 return indexes 122 123 def schema_name_converter(self, name): 124 """Convert to lowercase for case-sensitive schema name comparison.""" 125 return name.lower() 126 127 def get_schema_list(self, cursor): 128 "Returns a list of schemas that exist in the database." 129 sql = """ 130 select distinct username 131 from all_users, all_objects 132 where username = owner 133 """ 134 cursor.execute(sql) 135 return [schema.lower() for (schema,) in cursor] 136 137 def get_schema_table_list(self, cursor, schema): 138 "Returns a list of tables in a specific schema." 139 sql = """ 140 select table_name from all_tables 141 where owner = upper(%s) 142 """ 143 cursor.execute(sql, [schema]) 144 return [table.lower() for (table,) in cursor] -
django/db/backends/postgresql/base.py
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
a b 80 80 81 81 class DatabaseFeatures(BaseDatabaseFeatures): 82 82 uses_savepoints = True 83 default_schema_name = u'public' 83 84 84 85 class DatabaseWrapper(BaseDatabaseWrapper): 85 86 operators = { -
django/db/backends/postgresql/creation.py
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
a b 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/postgresql_psycopg2/base.py
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
a b 67 67 class DatabaseFeatures(BaseDatabaseFeatures): 68 68 needs_datetime_string_cast = False 69 69 can_return_id_from_insert = False 70 default_schema_name = u'public' 70 71 71 72 class DatabaseOperations(PostgresqlDatabaseOperations): 72 73 def last_executed_query(self, cursor, sql, params): -
django/db/backends/sqlite3/base.py
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
a b 98 98 (style.SQL_KEYWORD('DELETE'), 99 99 style.SQL_KEYWORD('FROM'), 100 100 style.SQL_FIELD(self.quote_name(table)) 101 ) for tablein tables]101 ) for (_, table) in tables] 102 102 # Note: No requirement for reset of auto-incremented indices (cf. other 103 103 # sql_flush() implementations). Just return SQL at this point 104 104 return sql -
django/db/backends/sqlite3/creation.py
diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
a b 38 38 "SQLite3 doesn't support constraints" 39 39 return [] 40 40 41 def _create_test_db(self, verbosity, autoclobber ):41 def _create_test_db(self, verbosity, autoclobber, schemas): 42 42 test_database_name = self.connection.settings_dict['TEST_NAME'] 43 43 if test_database_name and test_database_name != ":memory:": 44 44 # Erase the old test database -
django/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
a b 877 877 if isinstance(self.rel.to, basestring): 878 878 target = self.rel.to 879 879 else: 880 target = self.rel.to._meta. db_table880 target = self.rel.to._meta.qualified_name 881 881 cls._meta.duplicate_targets[self.column] = (target, "o2m") 882 882 883 883 def contribute_to_related_class(self, cls, related): … … 966 966 to = to.lower() 967 967 meta = type('Meta', (object,), { 968 968 'db_table': field._get_m2m_db_table(klass._meta), 969 'db_schema': field._get_m2m_db_schema(klass._meta), 969 970 'managed': managed, 970 971 'auto_created': klass, 971 972 'app_label': klass._meta.app_label, … … 995 996 through=kwargs.pop('through', None)) 996 997 997 998 self.db_table = kwargs.pop('db_table', None) 999 self.db_schema = kwargs.pop('db_schema', '') 998 1000 if kwargs['rel'].through is not None: 999 1001 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 1000 1002 … … 1016 1018 return util.truncate_name('%s_%s' % (opts.db_table, self.name), 1017 1019 connection.ops.max_name_length()) 1018 1020 1021 def _get_m2m_db_schema(self, opts): 1022 "Function that can be curried to provide the m2m schema name for this relation" 1023 if self.rel.through is not None and self.rel.through._meta.db_schema: 1024 return self.rel.through._meta.db_schema 1025 return self.db_schema 1026 1027 def _get_m2m_qualified_name(self, opts): 1028 "Function that can be curried to provide the qualified m2m table name for this relation" 1029 schema = self._get_m2m_db_schema(opts) 1030 table = self._get_m2m_db_table(opts) 1031 return connection.ops.prep_db_table(schema, table) 1032 1019 1033 def _get_m2m_attr(self, related, attr): 1020 1034 "Function that can be curried to provide the source accessor or DB column name for the m2m table" 1021 1035 cache_attr = '_m2m_%s_cache' % attr … … 1105 1119 1106 1120 # Set up the accessor for the m2m table name for the relation 1107 1121 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 1122 self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta) 1123 self.m2m_qualified_name = curry(self._get_m2m_qualified_name, 1124 cls._meta) 1108 1125 1109 1126 # Populate some necessary rel arguments so that cross-app relations 1110 1127 # work correctly. … … 1116 1133 if isinstance(self.rel.to, basestring): 1117 1134 target = self.rel.to 1118 1135 else: 1119 target = self.rel.to._meta. db_table1136 target = self.rel.to._meta.qualified_name 1120 1137 cls._meta.duplicate_targets[self.column] = (target, "m2m") 1121 1138 1122 1139 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 21 21 might not have all the pieces in place at that time. 22 22 """ 23 23 if not self.query.tables: 24 self.query.join((None, self.query.model._meta. db_table, None, None))24 self.query.join((None, self.query.model._meta.qualified_name, None, None)) 25 25 if (not self.query.select and self.query.default_cols and not 26 26 self.query.included_inherited_models): 27 27 self.query.setup_inherited_models() … … 248 248 alias = start_alias 249 249 else: 250 250 link_field = opts.get_ancestor_link(model) 251 alias = self.query.join((start_alias, model._meta. db_table,251 alias = self.query.join((start_alias, model._meta.qualified_name, 252 252 link_field.column, model._meta.pk.column)) 253 253 seen[model] = alias 254 254 else: … … 449 449 result.append('%s%s%s' % (connector, qn(name), alias_str)) 450 450 first = False 451 451 for t in self.query.extra_tables: 452 alias, unused = self.query.table_alias( t)452 alias, unused = self.query.table_alias(qn(t)) 453 453 # Only add the alias if it's not already present (the table_alias() 454 454 # calls increments the refcount, so an alias refcount of one means 455 455 # this is the only reference. … … 529 529 # what "used" specifies). 530 530 avoid = avoid_set.copy() 531 531 dupe_set = orig_dupe_set.copy() 532 table = f.rel.to._meta. db_table532 table = f.rel.to._meta.qualified_name 533 533 promote = nullable or f.null 534 534 if model: 535 535 int_opts = opts … … 550 550 ()) 551 551 dupe_set.add((opts, lhs_col)) 552 552 int_opts = int_model._meta 553 alias = self.query.join((alias, int_opts. db_table, lhs_col,553 alias = self.query.join((alias, int_opts.qualified_name, lhs_col, 554 554 int_opts.pk.column), exclusions=used, 555 555 promote=promote) 556 556 alias_chain.append(alias) … … 602 602 # what "used" specifies). 603 603 avoid = avoid_set.copy() 604 604 dupe_set = orig_dupe_set.copy() 605 table = model._meta. db_table605 table = model._meta.qualified_name 606 606 607 607 int_opts = opts 608 608 alias = root_alias … … 625 625 dupe_set.add((opts, lhs_col)) 626 626 int_opts = int_model._meta 627 627 alias = self.query.join( 628 (alias, int_opts. db_table, lhs_col, int_opts.pk.column),628 (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column), 629 629 exclusions=used, promote=True, reuse=used 630 630 ) 631 631 alias_chain.append(alias) … … 766 766 # going to be column names (so we can avoid the extra overhead). 767 767 qn = self.connection.ops.quote_name 768 768 opts = self.query.model._meta 769 result = ['INSERT INTO %s' % qn(opts.db_table)]769 result = ['INSERT INTO %s' % opts.qualified_name] 770 770 result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns])) 771 771 values = [self.placeholder(*v) for v in self.query.values] 772 772 result.append('VALUES (%s)' % ', '.join(values)) 773 773 params = self.query.params 774 774 if self.return_id and self.connection.features.can_return_id_from_insert: 775 col = "%s.%s" % ( qn(opts.db_table), qn(opts.pk.column))775 col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column)) 776 776 r_fmt, r_params = self.connection.ops.return_insert_id() 777 777 result.append(r_fmt % col) 778 778 params = params + r_params … … 786 786 if self.connection.features.can_return_id_from_insert: 787 787 return self.connection.ops.fetch_returned_insert_id(cursor) 788 788 return self.connection.ops.last_insert_id(cursor, 789 self.query.model._meta.db_table, self.query.model._meta.pk.column) 789 self.query.model._meta.db_schema, self.query.model._meta.db_table, 790 self.query.model._meta.pk.column) 790 791 791 792 792 793 class SQLDeleteCompiler(SQLCompiler): -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
a b 587 587 Callback used by deferred_to_columns(). The "target" parameter should 588 588 be a set instance. 589 589 """ 590 table = model._meta. db_table590 table = model._meta.qualified_name 591 591 if table not in target: 592 592 target[table] = set() 593 593 for field in fields: 594 594 target[table].add(field.column) 595 595 596 596 597 597 598 def table_alias(self, table_name, create=False): 598 599 """ 599 600 Returns a table alias for the given table_name and whether this is a … … 769 770 alias = self.tables[0] 770 771 self.ref_alias(alias) 771 772 else: 772 alias = self.join((None, self.model._meta.db_table, None, None)) 773 alias = self.join((None, self.model._meta.qualified_name, 774 None, None)) 773 775 return alias 774 776 775 777 def count_active_tables(self): … … 882 884 seen[model] = root_alias 883 885 else: 884 886 link_field = opts.get_ancestor_link(model) 885 seen[model] = self.join((root_alias, model._meta.db_table, 887 seen[model] = self.join((root_alias, 888 model._meta.qualified_name, 886 889 link_field.column, model._meta.pk.column)) 887 890 self.included_inherited_models = seen 888 891 … … 1207 1210 (id(opts), lhs_col), ())) 1208 1211 dupe_set.add((opts, lhs_col)) 1209 1212 opts = int_model._meta 1210 alias = self.join((alias, opts.db_table, lhs_col, 1211 opts.pk.column), exclusions=exclusions) 1213 alias = self.join((alias, opts.qualified_name, 1214 lhs_col, opts.pk.column), 1215 exclusions=exclusions) 1212 1216 joins.append(alias) 1213 1217 exclusions.add(alias) 1214 1218 for (dupe_opts, dupe_col) in dupe_set: … … 1233 1237 (table1, from_col1, to_col1, table2, from_col2, 1234 1238 to_col2, opts, target) = cached_data 1235 1239 else: 1236 table1 = field.m2m_ db_table()1240 table1 = field.m2m_qualified_name() 1237 1241 from_col1 = opts.pk.column 1238 1242 to_col1 = field.m2m_column_name() 1239 1243 opts = field.rel.to._meta 1240 table2 = opts. db_table1244 table2 = opts.qualified_name 1241 1245 from_col2 = field.m2m_reverse_name() 1242 1246 to_col2 = opts.pk.column 1243 1247 target = opts.pk … … 1264 1268 else: 1265 1269 opts = field.rel.to._meta 1266 1270 target = field.rel.get_related_field() 1267 table = opts. db_table1271 table = opts.qualified_name 1268 1272 from_col = field.column 1269 1273 to_col = target.column 1270 1274 orig_opts._join_cache[name] = (table, from_col, to_col, … … 1286 1290 (table1, from_col1, to_col1, table2, from_col2, 1287 1291 to_col2, opts, target) = cached_data 1288 1292 else: 1289 table1 = field.m2m_ db_table()1293 table1 = field.m2m_qualified_name() 1290 1294 from_col1 = opts.pk.column 1291 1295 to_col1 = field.m2m_reverse_name() 1292 1296 opts = orig_field.opts 1293 table2 = opts. db_table1297 table2 = opts.qualified_name 1294 1298 from_col2 = field.m2m_column_name() 1295 1299 to_col2 = opts.pk.column 1296 1300 target = opts.pk … … 1313 1317 local_field = opts.get_field_by_name( 1314 1318 field.rel.field_name)[0] 1315 1319 opts = orig_field.opts 1316 table = opts. db_table1320 table = opts.qualified_name 1317 1321 from_col = local_field.column 1318 1322 to_col = field.column 1319 1323 target = opts.pk … … 1569 1573 else: 1570 1574 opts = self.model._meta 1571 1575 if not self.select: 1572 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column), 1576 count = self.aggregates_module.Count((self.join((None, 1577 opts.qualified_name, None, None)), opts.pk.column), 1573 1578 is_summary=True, distinct=True) 1574 1579 else: 1575 1580 # Because of SQL portability issues, multi-column, distinct -
django/db/models/sql/subqueries.py
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
a b 38 38 field = self.model._meta.pk 39 39 where.add((Constraint(None, field.column, field), 'in', 40 40 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 41 self.do_query(self.model._meta. db_table, where, using=using)41 self.do_query(self.model._meta.qualified_name, where, using=using) 42 42 43 43 class UpdateQuery(Query): 44 44 """ … … 154 154 extras.update(kwargs) 155 155 return super(InsertQuery, self).clone(klass, **extras) 156 156 157 157 158 def insert_values(self, insert_values, raw_values=False): 158 159 """ 159 160 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