Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

schema.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import psycopg2
  2. from django.db.backends.base.schema import BaseDatabaseSchemaEditor
  3. class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
  4. sql_alter_column_type = "ALTER COLUMN %(column)s TYPE %(type)s USING %(column)s::%(type)s"
  5. sql_create_sequence = "CREATE SEQUENCE %(sequence)s"
  6. sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE"
  7. sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s"
  8. sql_create_index = "CREATE INDEX %(name)s ON %(table)s%(using)s (%(columns)s)%(extra)s"
  9. sql_create_varchar_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s varchar_pattern_ops)%(extra)s"
  10. sql_create_text_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s text_pattern_ops)%(extra)s"
  11. sql_delete_index = "DROP INDEX IF EXISTS %(name)s"
  12. # Setting the constraint to IMMEDIATE runs any deferred checks to allow
  13. # dropping it in the same transaction.
  14. sql_delete_fk = "SET CONSTRAINTS %(name)s IMMEDIATE; ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
  15. sql_delete_procedure = 'DROP FUNCTION %(procedure)s(%(param_types)s)'
  16. def quote_value(self, value):
  17. return psycopg2.extensions.adapt(value)
  18. def _field_indexes_sql(self, model, field):
  19. output = super()._field_indexes_sql(model, field)
  20. like_index_statement = self._create_like_index_sql(model, field)
  21. if like_index_statement is not None:
  22. output.append(like_index_statement)
  23. return output
  24. def _create_like_index_sql(self, model, field):
  25. """
  26. Return the statement to create an index with varchar operator pattern
  27. when the column type is 'varchar' or 'text', otherwise return None.
  28. """
  29. db_type = field.db_type(connection=self.connection)
  30. if db_type is not None and (field.db_index or field.unique):
  31. # Fields with database column types of `varchar` and `text` need
  32. # a second index that specifies their operator class, which is
  33. # needed when performing correct LIKE queries outside the
  34. # C locale. See #12234.
  35. #
  36. # The same doesn't apply to array fields such as varchar[size]
  37. # and text[size], so skip them.
  38. if '[' in db_type:
  39. return None
  40. if db_type.startswith('varchar'):
  41. return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_varchar_index)
  42. elif db_type.startswith('text'):
  43. return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_text_index)
  44. return None
  45. def _alter_column_type_sql(self, model, old_field, new_field, new_type):
  46. """Make ALTER TYPE with SERIAL make sense."""
  47. table = model._meta.db_table
  48. if new_type.lower() in ("serial", "bigserial"):
  49. column = new_field.column
  50. sequence_name = "%s_%s_seq" % (table, column)
  51. col_type = "integer" if new_type.lower() == "serial" else "bigint"
  52. return (
  53. (
  54. self.sql_alter_column_type % {
  55. "column": self.quote_name(column),
  56. "type": col_type,
  57. },
  58. [],
  59. ),
  60. [
  61. (
  62. self.sql_delete_sequence % {
  63. "sequence": self.quote_name(sequence_name),
  64. },
  65. [],
  66. ),
  67. (
  68. self.sql_create_sequence % {
  69. "sequence": self.quote_name(sequence_name),
  70. },
  71. [],
  72. ),
  73. (
  74. self.sql_alter_column % {
  75. "table": self.quote_name(table),
  76. "changes": self.sql_alter_column_default % {
  77. "column": self.quote_name(column),
  78. "default": "nextval('%s')" % self.quote_name(sequence_name),
  79. }
  80. },
  81. [],
  82. ),
  83. (
  84. self.sql_set_sequence_max % {
  85. "table": self.quote_name(table),
  86. "column": self.quote_name(column),
  87. "sequence": self.quote_name(sequence_name),
  88. },
  89. [],
  90. ),
  91. ],
  92. )
  93. else:
  94. return super()._alter_column_type_sql(model, old_field, new_field, new_type)
  95. def _alter_field(self, model, old_field, new_field, old_type, new_type,
  96. old_db_params, new_db_params, strict=False):
  97. # Drop indexes on varchar/text/citext columns that are changing to a
  98. # different type.
  99. if (old_field.db_index or old_field.unique) and (
  100. (old_type.startswith('varchar') and not new_type.startswith('varchar')) or
  101. (old_type.startswith('text') and not new_type.startswith('text')) or
  102. (old_type.startswith('citext') and not new_type.startswith('citext'))
  103. ):
  104. index_name = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
  105. self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
  106. super()._alter_field(
  107. model, old_field, new_field, old_type, new_type, old_db_params,
  108. new_db_params, strict,
  109. )
  110. # Added an index? Create any PostgreSQL-specific indexes.
  111. if ((not (old_field.db_index or old_field.unique) and new_field.db_index) or
  112. (not old_field.unique and new_field.unique)):
  113. like_index_statement = self._create_like_index_sql(model, new_field)
  114. if like_index_statement is not None:
  115. self.execute(like_index_statement)
  116. # Removed an index? Drop any PostgreSQL-specific indexes.
  117. if old_field.unique and not (new_field.db_index or new_field.unique):
  118. index_to_remove = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
  119. self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_to_remove))