hstore_unique.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from psqlextra.fields import HStoreField
  2. class HStoreUniqueSchemaEditorSideEffect:
  3. sql_hstore_unique_create = (
  4. "CREATE UNIQUE INDEX IF NOT EXISTS " "{name} ON {table} " "({columns})"
  5. )
  6. sql_hstore_unique_rename = (
  7. "ALTER INDEX " "{old_name} " "RENAME TO " "{new_name}"
  8. )
  9. sql_hstore_unique_drop = "DROP INDEX IF EXISTS {name}"
  10. def create_model(self, model):
  11. """Ran when a new model is created."""
  12. for field in model._meta.local_fields:
  13. if not isinstance(field, HStoreField):
  14. continue
  15. self.add_field(model, field)
  16. def delete_model(self, model):
  17. """Ran when a model is being deleted."""
  18. for field in model._meta.local_fields:
  19. if not isinstance(field, HStoreField):
  20. continue
  21. self.remove_field(model, field)
  22. def alter_db_table(self, model, old_db_table, new_db_table):
  23. """Ran when the name of a model is changed."""
  24. for field in model._meta.local_fields:
  25. if not isinstance(field, HStoreField):
  26. continue
  27. for keys in self._iterate_uniqueness_keys(field):
  28. self._rename_hstore_unique(
  29. old_db_table, new_db_table, field, field, keys
  30. )
  31. def add_field(self, model, field):
  32. """Ran when a field is added to a model."""
  33. for keys in self._iterate_uniqueness_keys(field):
  34. self._create_hstore_unique(model, field, keys)
  35. def remove_field(self, model, field):
  36. """Ran when a field is removed from a model."""
  37. for keys in self._iterate_uniqueness_keys(field):
  38. self._drop_hstore_unique(model, field, keys)
  39. def alter_field(self, model, old_field, new_field, strict=False):
  40. """Ran when the configuration on a field changed."""
  41. is_old_field_hstore = isinstance(old_field, HStoreField)
  42. is_new_field_hstore = isinstance(new_field, HStoreField)
  43. if not is_old_field_hstore and not is_new_field_hstore:
  44. return
  45. old_uniqueness = getattr(old_field, "uniqueness", []) or []
  46. new_uniqueness = getattr(new_field, "uniqueness", []) or []
  47. # handle field renames before moving on
  48. if str(old_field.column) != str(new_field.column):
  49. for keys in self._iterate_uniqueness_keys(old_field):
  50. self._rename_hstore_unique(
  51. model._meta.db_table,
  52. model._meta.db_table,
  53. old_field,
  54. new_field,
  55. keys,
  56. )
  57. # drop the indexes for keys that have been removed
  58. for keys in old_uniqueness:
  59. if keys not in new_uniqueness:
  60. self._drop_hstore_unique(
  61. model, old_field, self._compose_keys(keys)
  62. )
  63. # create new indexes for keys that have been added
  64. for keys in new_uniqueness:
  65. if keys not in old_uniqueness:
  66. self._create_hstore_unique(
  67. model, new_field, self._compose_keys(keys)
  68. )
  69. def _create_hstore_unique(self, model, field, keys):
  70. """Creates a UNIQUE constraint for the specified hstore keys."""
  71. name = self._unique_constraint_name(model._meta.db_table, field, keys)
  72. columns = ["(%s->'%s')" % (field.column, key) for key in keys]
  73. sql = self.sql_hstore_unique_create.format(
  74. name=self.quote_name(name),
  75. table=self.quote_name(model._meta.db_table),
  76. columns=",".join(columns),
  77. )
  78. self.execute(sql)
  79. def _rename_hstore_unique(
  80. self, old_table_name, new_table_name, old_field, new_field, keys
  81. ):
  82. """Renames an existing UNIQUE constraint for the specified hstore
  83. keys."""
  84. old_name = self._unique_constraint_name(old_table_name, old_field, keys)
  85. new_name = self._unique_constraint_name(new_table_name, new_field, keys)
  86. sql = self.sql_hstore_unique_rename.format(
  87. old_name=self.quote_name(old_name),
  88. new_name=self.quote_name(new_name),
  89. )
  90. self.execute(sql)
  91. def _drop_hstore_unique(self, model, field, keys):
  92. """Drops a UNIQUE constraint for the specified hstore keys."""
  93. name = self._unique_constraint_name(model._meta.db_table, field, keys)
  94. sql = self.sql_hstore_unique_drop.format(name=self.quote_name(name))
  95. self.execute(sql)
  96. @staticmethod
  97. def _unique_constraint_name(table: str, field, keys):
  98. """Gets the name for a UNIQUE INDEX that applies to one or more keys in
  99. a hstore field.
  100. Arguments:
  101. table:
  102. The name of the table the field is
  103. a part of.
  104. field:
  105. The hstore field to create a
  106. UNIQUE INDEX for.
  107. key:
  108. The name of the hstore key
  109. to create the name for.
  110. This can also be a tuple
  111. of multiple names.
  112. Returns:
  113. The name for the UNIQUE index.
  114. """
  115. postfix = "_".join(keys)
  116. return "{table}_{field}_unique_{postfix}".format(
  117. table=table, field=field.column, postfix=postfix
  118. )
  119. def _iterate_uniqueness_keys(self, field):
  120. """Iterates over the keys marked as "unique" in the specified field.
  121. Arguments:
  122. field:
  123. The field of which key's to
  124. iterate over.
  125. """
  126. uniqueness = getattr(field, "uniqueness", None)
  127. if not uniqueness:
  128. return
  129. for keys in uniqueness:
  130. composed_keys = self._compose_keys(keys)
  131. yield composed_keys
  132. @staticmethod
  133. def _compose_keys(constraint):
  134. """Turns a string into a list of string or returns it as a list."""
  135. if isinstance(constraint, str):
  136. return [constraint]
  137. return constraint