indexes.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. from django.db import NotSupportedError
  2. from django.db.models import Func, Index
  3. from django.utils.functional import cached_property
  4. __all__ = [
  5. 'BloomIndex', 'BrinIndex', 'BTreeIndex', 'GinIndex', 'GistIndex',
  6. 'HashIndex', 'SpGistIndex',
  7. ]
  8. class PostgresIndex(Index):
  9. @cached_property
  10. def max_name_length(self):
  11. # Allow an index name longer than 30 characters when the suffix is
  12. # longer than the usual 3 character limit. The 30 character limit for
  13. # cross-database compatibility isn't applicable to PostgreSQL-specific
  14. # indexes.
  15. return Index.max_name_length - len(Index.suffix) + len(self.suffix)
  16. def create_sql(self, model, schema_editor, using='', **kwargs):
  17. self.check_supported(schema_editor)
  18. statement = super().create_sql(model, schema_editor, using=' USING %s' % self.suffix, **kwargs)
  19. with_params = self.get_with_params()
  20. if with_params:
  21. statement.parts['extra'] = 'WITH (%s) %s' % (
  22. ', '.join(with_params),
  23. statement.parts['extra'],
  24. )
  25. return statement
  26. def check_supported(self, schema_editor):
  27. pass
  28. def get_with_params(self):
  29. return []
  30. class BloomIndex(PostgresIndex):
  31. suffix = 'bloom'
  32. def __init__(self, *expressions, length=None, columns=(), **kwargs):
  33. super().__init__(*expressions, **kwargs)
  34. if len(self.fields) > 32:
  35. raise ValueError('Bloom indexes support a maximum of 32 fields.')
  36. if not isinstance(columns, (list, tuple)):
  37. raise ValueError('BloomIndex.columns must be a list or tuple.')
  38. if len(columns) > len(self.fields):
  39. raise ValueError(
  40. 'BloomIndex.columns cannot have more values than fields.'
  41. )
  42. if not all(0 < col <= 4095 for col in columns):
  43. raise ValueError(
  44. 'BloomIndex.columns must contain integers from 1 to 4095.',
  45. )
  46. if length is not None and not 0 < length <= 4096:
  47. raise ValueError(
  48. 'BloomIndex.length must be None or an integer from 1 to 4096.',
  49. )
  50. self.length = length
  51. self.columns = columns
  52. def deconstruct(self):
  53. path, args, kwargs = super().deconstruct()
  54. if self.length is not None:
  55. kwargs['length'] = self.length
  56. if self.columns:
  57. kwargs['columns'] = self.columns
  58. return path, args, kwargs
  59. def get_with_params(self):
  60. with_params = []
  61. if self.length is not None:
  62. with_params.append('length = %d' % self.length)
  63. if self.columns:
  64. with_params.extend(
  65. 'col%d = %d' % (i, v)
  66. for i, v in enumerate(self.columns, start=1)
  67. )
  68. return with_params
  69. class BrinIndex(PostgresIndex):
  70. suffix = 'brin'
  71. def __init__(self, *expressions, autosummarize=None, pages_per_range=None, **kwargs):
  72. if pages_per_range is not None and pages_per_range <= 0:
  73. raise ValueError('pages_per_range must be None or a positive integer')
  74. self.autosummarize = autosummarize
  75. self.pages_per_range = pages_per_range
  76. super().__init__(*expressions, **kwargs)
  77. def deconstruct(self):
  78. path, args, kwargs = super().deconstruct()
  79. if self.autosummarize is not None:
  80. kwargs['autosummarize'] = self.autosummarize
  81. if self.pages_per_range is not None:
  82. kwargs['pages_per_range'] = self.pages_per_range
  83. return path, args, kwargs
  84. def check_supported(self, schema_editor):
  85. if self.autosummarize and not schema_editor.connection.features.has_brin_autosummarize:
  86. raise NotSupportedError('BRIN option autosummarize requires PostgreSQL 10+.')
  87. def get_with_params(self):
  88. with_params = []
  89. if self.autosummarize is not None:
  90. with_params.append('autosummarize = %s' % ('on' if self.autosummarize else 'off'))
  91. if self.pages_per_range is not None:
  92. with_params.append('pages_per_range = %d' % self.pages_per_range)
  93. return with_params
  94. class BTreeIndex(PostgresIndex):
  95. suffix = 'btree'
  96. def __init__(self, *expressions, fillfactor=None, **kwargs):
  97. self.fillfactor = fillfactor
  98. super().__init__(*expressions, **kwargs)
  99. def deconstruct(self):
  100. path, args, kwargs = super().deconstruct()
  101. if self.fillfactor is not None:
  102. kwargs['fillfactor'] = self.fillfactor
  103. return path, args, kwargs
  104. def get_with_params(self):
  105. with_params = []
  106. if self.fillfactor is not None:
  107. with_params.append('fillfactor = %d' % self.fillfactor)
  108. return with_params
  109. class GinIndex(PostgresIndex):
  110. suffix = 'gin'
  111. def __init__(self, *expressions, fastupdate=None, gin_pending_list_limit=None, **kwargs):
  112. self.fastupdate = fastupdate
  113. self.gin_pending_list_limit = gin_pending_list_limit
  114. super().__init__(*expressions, **kwargs)
  115. def deconstruct(self):
  116. path, args, kwargs = super().deconstruct()
  117. if self.fastupdate is not None:
  118. kwargs['fastupdate'] = self.fastupdate
  119. if self.gin_pending_list_limit is not None:
  120. kwargs['gin_pending_list_limit'] = self.gin_pending_list_limit
  121. return path, args, kwargs
  122. def get_with_params(self):
  123. with_params = []
  124. if self.gin_pending_list_limit is not None:
  125. with_params.append('gin_pending_list_limit = %d' % self.gin_pending_list_limit)
  126. if self.fastupdate is not None:
  127. with_params.append('fastupdate = %s' % ('on' if self.fastupdate else 'off'))
  128. return with_params
  129. class GistIndex(PostgresIndex):
  130. suffix = 'gist'
  131. def __init__(self, *expressions, buffering=None, fillfactor=None, **kwargs):
  132. self.buffering = buffering
  133. self.fillfactor = fillfactor
  134. super().__init__(*expressions, **kwargs)
  135. def deconstruct(self):
  136. path, args, kwargs = super().deconstruct()
  137. if self.buffering is not None:
  138. kwargs['buffering'] = self.buffering
  139. if self.fillfactor is not None:
  140. kwargs['fillfactor'] = self.fillfactor
  141. return path, args, kwargs
  142. def get_with_params(self):
  143. with_params = []
  144. if self.buffering is not None:
  145. with_params.append('buffering = %s' % ('on' if self.buffering else 'off'))
  146. if self.fillfactor is not None:
  147. with_params.append('fillfactor = %d' % self.fillfactor)
  148. return with_params
  149. def check_supported(self, schema_editor):
  150. if self.include and not schema_editor.connection.features.supports_covering_gist_indexes:
  151. raise NotSupportedError('Covering GiST indexes requires PostgreSQL 12+.')
  152. class HashIndex(PostgresIndex):
  153. suffix = 'hash'
  154. def __init__(self, *expressions, fillfactor=None, **kwargs):
  155. self.fillfactor = fillfactor
  156. super().__init__(*expressions, **kwargs)
  157. def deconstruct(self):
  158. path, args, kwargs = super().deconstruct()
  159. if self.fillfactor is not None:
  160. kwargs['fillfactor'] = self.fillfactor
  161. return path, args, kwargs
  162. def get_with_params(self):
  163. with_params = []
  164. if self.fillfactor is not None:
  165. with_params.append('fillfactor = %d' % self.fillfactor)
  166. return with_params
  167. class SpGistIndex(PostgresIndex):
  168. suffix = 'spgist'
  169. def __init__(self, *expressions, fillfactor=None, **kwargs):
  170. self.fillfactor = fillfactor
  171. super().__init__(*expressions, **kwargs)
  172. def deconstruct(self):
  173. path, args, kwargs = super().deconstruct()
  174. if self.fillfactor is not None:
  175. kwargs['fillfactor'] = self.fillfactor
  176. return path, args, kwargs
  177. def get_with_params(self):
  178. with_params = []
  179. if self.fillfactor is not None:
  180. with_params.append('fillfactor = %d' % self.fillfactor)
  181. return with_params
  182. class OpClass(Func):
  183. template = '%(expressions)s %(name)s'
  184. def __init__(self, expression, name):
  185. super().__init__(expression, name=name)