schema.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import copy
  2. import datetime
  3. import binascii
  4. from django.utils import six
  5. from django.utils.text import force_text
  6. from django.db.backends.schema import BaseDatabaseSchemaEditor
  7. from django.db.utils import DatabaseError
  8. class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
  9. sql_create_column = "ALTER TABLE %(table)s ADD %(column)s %(definition)s"
  10. sql_alter_column_type = "MODIFY %(column)s %(type)s"
  11. sql_alter_column_null = "MODIFY %(column)s NULL"
  12. sql_alter_column_not_null = "MODIFY %(column)s NOT NULL"
  13. sql_alter_column_default = "MODIFY %(column)s DEFAULT %(default)s"
  14. sql_alter_column_no_default = "MODIFY %(column)s DEFAULT NULL"
  15. sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s"
  16. sql_delete_table = "DROP TABLE %(table)s CASCADE CONSTRAINTS"
  17. def quote_value(self, value):
  18. if isinstance(value, (datetime.date, datetime.time, datetime.datetime)):
  19. return "'%s'" % value
  20. elif isinstance(value, six.string_types):
  21. return "'%s'" % six.text_type(value).replace("\'", "\'\'")
  22. elif isinstance(value, six.buffer_types):
  23. return "'%s'" % force_text(binascii.hexlify(value))
  24. elif isinstance(value, bool):
  25. return "1" if value else "0"
  26. else:
  27. return str(value)
  28. def delete_model(self, model):
  29. # Run superclass action
  30. super(DatabaseSchemaEditor, self).delete_model(model)
  31. # Clean up any autoincrement trigger
  32. self.execute("""
  33. DECLARE
  34. i INTEGER;
  35. BEGIN
  36. SELECT COUNT(*) INTO i FROM USER_CATALOG
  37. WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
  38. IF i = 1 THEN
  39. EXECUTE IMMEDIATE 'DROP SEQUENCE "%(sq_name)s"';
  40. END IF;
  41. END;
  42. /""" % {'sq_name': self.connection.ops._get_sequence_name(model._meta.db_table)})
  43. def alter_field(self, model, old_field, new_field, strict=False):
  44. try:
  45. # Run superclass action
  46. super(DatabaseSchemaEditor, self).alter_field(model, old_field, new_field, strict)
  47. except DatabaseError as e:
  48. description = str(e)
  49. # If we're changing to/from LOB fields, we need to do a
  50. # SQLite-ish workaround
  51. if 'ORA-22858' in description or 'ORA-22859' in description:
  52. self._alter_field_lob_workaround(model, old_field, new_field)
  53. else:
  54. raise
  55. def _alter_field_lob_workaround(self, model, old_field, new_field):
  56. """
  57. Oracle refuses to change a column type from/to LOB to/from a regular
  58. column. In Django, this shows up when the field is changed from/to
  59. a TextField.
  60. What we need to do instead is:
  61. - Add the desired field with a temporary name
  62. - Update the table to transfer values from old to new
  63. - Drop old column
  64. - Rename the new column
  65. """
  66. # Make a new field that's like the new one but with a temporary
  67. # column name.
  68. new_temp_field = copy.deepcopy(new_field)
  69. new_temp_field.column = self._generate_temp_name(new_field.column)
  70. # Add it
  71. self.add_field(model, new_temp_field)
  72. # Transfer values across
  73. self.execute("UPDATE %s set %s=%s" % (
  74. self.quote_name(model._meta.db_table),
  75. self.quote_name(new_temp_field.column),
  76. self.quote_name(old_field.column),
  77. ))
  78. # Drop the old field
  79. self.remove_field(model, old_field)
  80. # Rename the new field
  81. self.alter_field(model, new_temp_field, new_field)
  82. # Close the connection to force cx_Oracle to get column types right
  83. # on a new cursor
  84. self.connection.close()
  85. def normalize_name(self, name):
  86. """
  87. Get the properly shortened and uppercased identifier as returned by quote_name(), but without the actual quotes.
  88. """
  89. nn = self.quote_name(name)
  90. if nn[0] == '"' and nn[-1] == '"':
  91. nn = nn[1:-1]
  92. return nn
  93. def _generate_temp_name(self, for_name):
  94. """
  95. Generates temporary names for workarounds that need temp columns
  96. """
  97. suffix = hex(hash(for_name)).upper()[1:]
  98. return self.normalize_name(for_name + "_" + suffix)
  99. def prepare_default(self, value):
  100. return self.quote_value(value)