2 from django.core.management.color import no_style
 
   3 from django.db import connection, transaction, models
 
   4 from django.db.backends.util import truncate_name
 
   5 from django.dispatch import dispatcher
 
   6 from django.conf import settings
 
   8 class DatabaseOperations(object):
 
  11     Generic SQL implementation of the DatabaseOperations.
 
  12     Some of this code comes from Django Evolution.
 
  17         self.deferred_sql = []
 
  20     def execute(self, sql, params=[]):
 
  22         Executes the given SQL statement, with optional parameters.
 
  23         If the instance's debug attribute is True, prints out what it executes.
 
  25         cursor = connection.cursor()
 
  27             print "   = %s" % sql, params
 
  28         cursor.execute(sql, params)
 
  30             return cursor.fetchall()
 
  35     def add_deferred_sql(self, sql):
 
  37         Add a SQL statement to the deferred list, that won't be executed until
 
  38         this instance's execute_deferred_sql method is run.
 
  40         self.deferred_sql.append(sql)
 
  43     def execute_deferred_sql(self):
 
  45         Executes all deferred SQL, resetting the deferred_sql list
 
  47         for sql in self.deferred_sql:
 
  50         self.deferred_sql = []
 
  53     def create_table(self, table_name, fields):
 
  55         Creates the table 'table_name'. 'fields' is a tuple of fields,
 
  56         each repsented by a 2-part tuple of field name and a
 
  57         django.db.models.fields.Field object
 
  59         qn = connection.ops.quote_name
 
  61             self.column_sql(table_name, field_name, field)
 
  62             for field_name, field in fields
 
  65         self.execute('CREATE TABLE %s (%s);' % (qn(table_name), ', '.join([col for col in columns if col])))
 
  67     add_table = create_table # Alias for consistency's sake
 
  70     def rename_table(self, old_table_name, table_name):
 
  72         Renames the table 'old_table_name' to 'table_name'.
 
  74         if old_table_name == table_name:
 
  77         qn = connection.ops.quote_name
 
  78         params = (qn(old_table_name), qn(table_name))
 
  79         self.execute('ALTER TABLE %s RENAME TO %s;' % params)
 
  82     def delete_table(self, table_name):
 
  84         Deletes the table 'table_name'.
 
  86         qn = connection.ops.quote_name
 
  87         params = (qn(table_name), )
 
  88         self.execute('DROP TABLE %s;' % params)
 
  90     drop_table = delete_table
 
  93     def add_column(self, table_name, name, field):
 
  95         Adds the column 'name' to the table 'table_name'.
 
  96         Uses the 'field' paramater, a django.db.models.fields.Field instance,
 
  97         to generate the necessary sql
 
  99         @param table_name: The name of the table to add the column to
 
 100         @param name: The name of the column to add
 
 101         @param field: The field to use
 
 103         qn = connection.ops.quote_name
 
 104         sql = self.column_sql(table_name, name, field)
 
 110             sql = 'ALTER TABLE %s ADD COLUMN %s;' % params
 
 114     alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
 
 115     alter_string_set_null = 'ALTER COLUMN %(column)s SET NOT NULL'
 
 116     alter_string_drop_null = 'ALTER COLUMN %(column)s DROP NOT NULL'
 
 118     def alter_column(self, table_name, name, field):
 
 120         Alters the given column name so it will match the given field.
 
 121         Note that conversion between the two by the database must be possible.
 
 123         @param table_name: The name of the table to add the column to
 
 124         @param name: The name of the column to alter
 
 125         @param field: The new field definition to use
 
 128         # hook for the field to do any resolution prior to it's attributes being queried
 
 129         if hasattr(field, 'south_init'):
 
 132         qn = connection.ops.quote_name
 
 134         # First, change the type
 
 137             "type": field.db_type(),
 
 139         sqls = [self.alter_string_set_type % params]
 
 142         # Next, set any default
 
 147         if not field.null and field.has_default():
 
 148             default = field.get_default()
 
 149             if isinstance(default, basestring):
 
 150                 default = "'%s'" % default
 
 151             params += ("SET DEFAULT %s",)
 
 153             params += ("DROP DEFAULT",)
 
 155         sqls.append('ALTER COLUMN %s %s ' % params)
 
 161             "type": field.db_type(),
 
 164             sqls.append(self.alter_string_drop_null % params)
 
 166             sqls.append(self.alter_string_set_null % params)
 
 171         self.execute("ALTER TABLE %s %s;" % (qn(table_name), ", ".join(sqls)))
 
 174     def column_sql(self, table_name, field_name, field, tablespace=''):
 
 176         Creates the SQL snippet for a column. Used by add_column and add_table.
 
 178         qn = connection.ops.quote_name
 
 180         field.set_attributes_from_name(field_name)
 
 182         # hook for the field to do any resolution prior to it's attributes being queried
 
 183         if hasattr(field, 'south_init'):
 
 186         sql = field.db_type()
 
 188             field_output = [qn(field.column), sql]
 
 189             field_output.append('%sNULL' % (not field.null and 'NOT ' or ''))
 
 190             if field.primary_key:
 
 191                 field_output.append('PRIMARY KEY')
 
 193                 field_output.append('UNIQUE')
 
 195             tablespace = field.db_tablespace or tablespace
 
 196             if tablespace and connection.features.supports_tablespaces and field.unique:
 
 197                 # We must specify the index tablespace inline, because we
 
 198                 # won't be generating a CREATE INDEX statement for this field.
 
 199                 field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
 
 201             sql = ' '.join(field_output)
 
 203             # if the field is "NOT NULL" and a default value is provided, create the column with it
 
 204             # this allows the addition of a NOT NULL field to a table with existing rows
 
 205             if not field.null and field.has_default():
 
 206                 default = field.get_default()
 
 207                 if isinstance(default, basestring):
 
 208                     default = "'%s'" % default.replace("'", "''")
 
 210                 sqlparams = (default)
 
 213                 self.add_deferred_sql(
 
 214                     self.foreign_key_sql(
 
 217                         field.rel.to._meta.db_table,
 
 218                         field.rel.to._meta.get_field(field.rel.field_name).column
 
 222             if field.db_index and not field.unique:
 
 223                 self.add_deferred_sql(self.create_index_sql(table_name, [field.column]))
 
 225         if hasattr(field, 'post_create_sql'):
 
 227             for stmt in field.post_create_sql(style, table_name):
 
 228                 self.add_deferred_sql(stmt)
 
 231             return sql % sqlparams
 
 235     def foreign_key_sql(self, from_table_name, from_column_name, to_table_name, to_column_name):
 
 237         Generates a full SQL statement to add a foreign key constraint
 
 239         constraint_name = '%s_refs_%s_%x' % (from_column_name, to_column_name, abs(hash((from_table_name, to_table_name))))
 
 240         return 'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % (
 
 242             truncate_name(constraint_name, connection.ops.max_name_length()),
 
 246             connection.ops.deferrable_sql() # Django knows this
 
 249     def create_index_name(self, table_name, column_names):
 
 251         Generate a unique name for the index
 
 253         index_unique_name = ''
 
 254         if len(column_names) > 1:
 
 255             index_unique_name = '_%x' % abs(hash((table_name, ','.join(column_names))))
 
 257         return '%s_%s%s' % (table_name, column_names[0], index_unique_name)
 
 259     def create_index_sql(self, table_name, column_names, unique=False, db_tablespace=''):
 
 261         Generates a create index statement on 'table_name' for a list of 'column_names'
 
 264             print "No column names supplied on which to create an index"
 
 267         if db_tablespace and connection.features.supports_tablespaces:
 
 268             tablespace_sql = ' ' + connection.ops.tablespace_sql(db_tablespace)
 
 272         index_name = self.create_index_name(table_name, column_names)
 
 273         qn = connection.ops.quote_name
 
 274         return 'CREATE %sINDEX %s ON %s (%s)%s;' % (
 
 275             unique and 'UNIQUE ' or '',
 
 278             ','.join([qn(field) for field in column_names]),
 
 282     def create_index(self, table_name, column_names, unique=False, db_tablespace=''):
 
 283         """ Executes a create index statement """
 
 284         sql = self.create_index_sql(table_name, column_names, unique, db_tablespace)
 
 288     def delete_index(self, table_name, column_names, db_tablespace=''):
 
 290         Deletes an index created with create_index.
 
 291         This is possible using only columns due to the deterministic
 
 292         index naming function which relies on column names.
 
 294         name = self.create_index_name(table_name, column_names)
 
 295         sql = "DROP INDEX %s" % name
 
 299     def delete_column(self, table_name, name):
 
 301         Deletes the column 'column_name' from the table 'table_name'.
 
 303         qn = connection.ops.quote_name
 
 304         params = (qn(table_name), qn(name))
 
 305         self.execute('ALTER TABLE %s DROP COLUMN %s CASCADE;' % params, [])
 
 308     def rename_column(self, table_name, old, new):
 
 310         Renames the column 'old' from the table 'table_name' to 'new'.
 
 312         raise NotImplementedError("rename_column has no generic SQL syntax")
 
 315     def start_transaction(self):
 
 317         Makes sure the following commands are inside a transaction.
 
 318         Must be followed by a (commit|rollback)_transaction call.
 
 320         transaction.commit_unless_managed()
 
 321         transaction.enter_transaction_management()
 
 322         transaction.managed(True)
 
 325     def commit_transaction(self):
 
 327         Commits the current transaction.
 
 328         Must be preceded by a start_transaction call.
 
 331         transaction.leave_transaction_management()
 
 334     def rollback_transaction(self):
 
 336         Rolls back the current transaction.
 
 337         Must be preceded by a start_transaction call.
 
 339         transaction.rollback()
 
 340         transaction.leave_transaction_management()
 
 343     def send_create_signal(self, app_label, model_names):
 
 345         Sends a post_syncdb signal for the model specified.
 
 347         If the model is not found (perhaps it's been deleted?),
 
 350         TODO: The behavior of django.contrib.* apps seems flawed in that
 
 351         they don't respect created_models.  Rather, they blindly execute
 
 352         over all models within the app sending the signal.  This is a
 
 353         patch we should push Django to make  For now, this should work.
 
 355         app = models.get_app(app_label)
 
 360         for model_name in model_names:
 
 361             model = models.get_model(app_label, model_name)
 
 363                 created_models.append(model)
 
 366             # syncdb defaults -- perhaps take these as options?
 
 370             if hasattr(dispatcher, "send"):
 
 371                 dispatcher.send(signal=models.signals.post_syncdb, sender=app,
 
 372                 app=app, created_models=created_models,
 
 373                 verbosity=verbosity, interactive=interactive)
 
 375                 models.signals.post_syncdb.send(sender=app,
 
 376                 app=app, created_models=created_models,
 
 377                 verbosity=verbosity, interactive=interactive)
 
 379     def mock_model(self, model_name, db_table, db_tablespace='', 
 
 380                     pk_field_name='id', pk_field_type=models.AutoField,
 
 383         Generates a MockModel class that provides enough information
 
 384         to be used by a foreign key/many-to-many relationship.
 
 386         Migrations should prefer to use these rather than actual models
 
 387         as models could get deleted over time, but these can remain in
 
 388         migration files forever.
 
 390         class MockOptions(object):
 
 392                 self.db_table = db_table
 
 393                 self.db_tablespace = db_tablespace or settings.DEFAULT_TABLESPACE
 
 394                 self.object_name = model_name
 
 395                 self.module_name = model_name.lower()
 
 397                 if pk_field_type == models.AutoField:
 
 398                     pk_field_kwargs['primary_key'] = True
 
 400                 self.pk = pk_field_type(**pk_field_kwargs)
 
 401                 self.pk.set_attributes_from_name(pk_field_name)
 
 402                 self.abstract = False
 
 404             def get_field_by_name(self, field_name):
 
 405                 # we only care about the pk field
 
 406                 return (self.pk, self.model, True, False)
 
 408             def get_field(self, name):
 
 409                 # we only care about the pk field
 
 412         class MockModel(object):
 
 415         # We need to return an actual class object here, not an instance
 
 416         MockModel._meta = MockOptions()
 
 417         MockModel._meta.model = MockModel