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.

ddl_references.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. """
  2. Helpers to manipulate deferred DDL statements that might need to be adjusted or
  3. discarded within when executing a migration.
  4. """
  5. class Reference:
  6. """Base class that defines the reference interface."""
  7. def references_table(self, table):
  8. """
  9. Return whether or not this instance references the specified table.
  10. """
  11. return False
  12. def references_column(self, table, column):
  13. """
  14. Return whether or not this instance references the specified column.
  15. """
  16. return False
  17. def rename_table_references(self, old_table, new_table):
  18. """
  19. Rename all references to the old_name to the new_table.
  20. """
  21. pass
  22. def rename_column_references(self, table, old_column, new_column):
  23. """
  24. Rename all references to the old_column to the new_column.
  25. """
  26. pass
  27. def __repr__(self):
  28. return '<%s %r>' % (self.__class__.__name__, str(self))
  29. def __str__(self):
  30. raise NotImplementedError('Subclasses must define how they should be converted to string.')
  31. class Table(Reference):
  32. """Hold a reference to a table."""
  33. def __init__(self, table, quote_name):
  34. self.table = table
  35. self.quote_name = quote_name
  36. def references_table(self, table):
  37. return self.table == table
  38. def rename_table_references(self, old_table, new_table):
  39. if self.table == old_table:
  40. self.table = new_table
  41. def __str__(self):
  42. return self.quote_name(self.table)
  43. class TableColumns(Table):
  44. """Base class for references to multiple columns of a table."""
  45. def __init__(self, table, columns):
  46. self.table = table
  47. self.columns = columns
  48. def references_column(self, table, column):
  49. return self.table == table and column in self.columns
  50. def rename_column_references(self, table, old_column, new_column):
  51. if self.table == table:
  52. for index, column in enumerate(self.columns):
  53. if column == old_column:
  54. self.columns[index] = new_column
  55. class Columns(TableColumns):
  56. """Hold a reference to one or many columns."""
  57. def __init__(self, table, columns, quote_name, col_suffixes=()):
  58. self.quote_name = quote_name
  59. self.col_suffixes = col_suffixes
  60. super().__init__(table, columns)
  61. def __str__(self):
  62. def col_str(column, idx):
  63. try:
  64. return self.quote_name(column) + self.col_suffixes[idx]
  65. except IndexError:
  66. return self.quote_name(column)
  67. return ', '.join(col_str(column, idx) for idx, column in enumerate(self.columns))
  68. class IndexName(TableColumns):
  69. """Hold a reference to an index name."""
  70. def __init__(self, table, columns, suffix, create_index_name):
  71. self.suffix = suffix
  72. self.create_index_name = create_index_name
  73. super().__init__(table, columns)
  74. def __str__(self):
  75. return self.create_index_name(self.table, self.columns, self.suffix)
  76. class IndexColumns(Columns):
  77. def __init__(self, table, columns, quote_name, col_suffixes=(), opclasses=()):
  78. self.opclasses = opclasses
  79. super().__init__(table, columns, quote_name, col_suffixes)
  80. def __str__(self):
  81. def col_str(column, idx):
  82. # Index.__init__() guarantees that self.opclasses is the same
  83. # length as self.columns.
  84. col = '{} {}'.format(self.quote_name(column), self.opclasses[idx])
  85. try:
  86. col = '{} {}'.format(col, self.col_suffixes[idx])
  87. except IndexError:
  88. pass
  89. return col
  90. return ', '.join(col_str(column, idx) for idx, column in enumerate(self.columns))
  91. class ForeignKeyName(TableColumns):
  92. """Hold a reference to a foreign key name."""
  93. def __init__(self, from_table, from_columns, to_table, to_columns, suffix_template, create_fk_name):
  94. self.to_reference = TableColumns(to_table, to_columns)
  95. self.suffix_template = suffix_template
  96. self.create_fk_name = create_fk_name
  97. super().__init__(from_table, from_columns,)
  98. def references_table(self, table):
  99. return super().references_table(table) or self.to_reference.references_table(table)
  100. def references_column(self, table, column):
  101. return (
  102. super().references_column(table, column) or
  103. self.to_reference.references_column(table, column)
  104. )
  105. def rename_table_references(self, old_table, new_table):
  106. super().rename_table_references(old_table, new_table)
  107. self.to_reference.rename_table_references(old_table, new_table)
  108. def rename_column_references(self, table, old_column, new_column):
  109. super().rename_column_references(table, old_column, new_column)
  110. self.to_reference.rename_column_references(table, old_column, new_column)
  111. def __str__(self):
  112. suffix = self.suffix_template % {
  113. 'to_table': self.to_reference.table,
  114. 'to_column': self.to_reference.columns[0],
  115. }
  116. return self.create_fk_name(self.table, self.columns, suffix)
  117. class Statement(Reference):
  118. """
  119. Statement template and formatting parameters container.
  120. Allows keeping a reference to a statement without interpolating identifiers
  121. that might have to be adjusted if they're referencing a table or column
  122. that is removed
  123. """
  124. def __init__(self, template, **parts):
  125. self.template = template
  126. self.parts = parts
  127. def references_table(self, table):
  128. return any(
  129. hasattr(part, 'references_table') and part.references_table(table)
  130. for part in self.parts.values()
  131. )
  132. def references_column(self, table, column):
  133. return any(
  134. hasattr(part, 'references_column') and part.references_column(table, column)
  135. for part in self.parts.values()
  136. )
  137. def rename_table_references(self, old_table, new_table):
  138. for part in self.parts.values():
  139. if hasattr(part, 'rename_table_references'):
  140. part.rename_table_references(old_table, new_table)
  141. def rename_column_references(self, table, old_column, new_column):
  142. for part in self.parts.values():
  143. if hasattr(part, 'rename_column_references'):
  144. part.rename_column_references(table, old_column, new_column)
  145. def __str__(self):
  146. return self.template % self.parts