subqueries.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. """
  2. Query subclasses which provide extra functionality beyond simple data retrieval.
  3. """
  4. from django.core.exceptions import FieldError
  5. from django.db.models.query_utils import Q
  6. from django.db.models.sql.constants import (
  7. CURSOR, GET_ITERATOR_CHUNK_SIZE, NO_RESULTS,
  8. )
  9. from django.db.models.sql.query import Query
  10. __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'AggregateQuery']
  11. class DeleteQuery(Query):
  12. """A DELETE SQL query."""
  13. compiler = 'SQLDeleteCompiler'
  14. def do_query(self, table, where, using):
  15. self.alias_map = {table: self.alias_map[table]}
  16. self.where = where
  17. cursor = self.get_compiler(using).execute_sql(CURSOR)
  18. if cursor:
  19. with cursor:
  20. return cursor.rowcount
  21. return 0
  22. def delete_batch(self, pk_list, using):
  23. """
  24. Set up and execute delete queries for all the objects in pk_list.
  25. More than one physical query may be executed if there are a
  26. lot of values in pk_list.
  27. """
  28. # number of objects deleted
  29. num_deleted = 0
  30. field = self.get_meta().pk
  31. for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
  32. self.where = self.where_class()
  33. self.add_q(Q(
  34. **{field.attname + '__in': pk_list[offset:offset + GET_ITERATOR_CHUNK_SIZE]}))
  35. num_deleted += self.do_query(self.get_meta().db_table, self.where, using=using)
  36. return num_deleted
  37. class UpdateQuery(Query):
  38. """An UPDATE SQL query."""
  39. compiler = 'SQLUpdateCompiler'
  40. def __init__(self, *args, **kwargs):
  41. super().__init__(*args, **kwargs)
  42. self._setup_query()
  43. def _setup_query(self):
  44. """
  45. Run on initialization and at the end of chaining. Any attributes that
  46. would normally be set in __init__() should go here instead.
  47. """
  48. self.values = []
  49. self.related_ids = None
  50. self.related_updates = {}
  51. def clone(self):
  52. obj = super().clone()
  53. obj.related_updates = self.related_updates.copy()
  54. return obj
  55. def update_batch(self, pk_list, values, using):
  56. self.add_update_values(values)
  57. for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
  58. self.where = self.where_class()
  59. self.add_q(Q(pk__in=pk_list[offset: offset + GET_ITERATOR_CHUNK_SIZE]))
  60. self.get_compiler(using).execute_sql(NO_RESULTS)
  61. def add_update_values(self, values):
  62. """
  63. Convert a dictionary of field name to value mappings into an update
  64. query. This is the entry point for the public update() method on
  65. querysets.
  66. """
  67. values_seq = []
  68. for name, val in values.items():
  69. field = self.get_meta().get_field(name)
  70. direct = not (field.auto_created and not field.concrete) or not field.concrete
  71. model = field.model._meta.concrete_model
  72. if not direct or (field.is_relation and field.many_to_many):
  73. raise FieldError(
  74. 'Cannot update model field %r (only non-relations and '
  75. 'foreign keys permitted).' % field
  76. )
  77. if model is not self.get_meta().concrete_model:
  78. self.add_related_update(model, field, val)
  79. continue
  80. values_seq.append((field, model, val))
  81. return self.add_update_fields(values_seq)
  82. def add_update_fields(self, values_seq):
  83. """
  84. Append a sequence of (field, model, value) triples to the internal list
  85. that will be used to generate the UPDATE query. Might be more usefully
  86. called add_update_targets() to hint at the extra information here.
  87. """
  88. for field, model, val in values_seq:
  89. if hasattr(val, 'resolve_expression'):
  90. # Resolve expressions here so that annotations are no longer needed
  91. val = val.resolve_expression(self, allow_joins=False, for_save=True)
  92. self.values.append((field, model, val))
  93. def add_related_update(self, model, field, value):
  94. """
  95. Add (name, value) to an update query for an ancestor model.
  96. Update are coalesced so that only one update query per ancestor is run.
  97. """
  98. self.related_updates.setdefault(model, []).append((field, None, value))
  99. def get_related_updates(self):
  100. """
  101. Return a list of query objects: one for each update required to an
  102. ancestor model. Each query will have the same filtering conditions as
  103. the current query but will only update a single table.
  104. """
  105. if not self.related_updates:
  106. return []
  107. result = []
  108. for model, values in self.related_updates.items():
  109. query = UpdateQuery(model)
  110. query.values = values
  111. if self.related_ids is not None:
  112. query.add_filter(('pk__in', self.related_ids))
  113. result.append(query)
  114. return result
  115. class InsertQuery(Query):
  116. compiler = 'SQLInsertCompiler'
  117. def __init__(self, *args, ignore_conflicts=False, **kwargs):
  118. super().__init__(*args, **kwargs)
  119. self.fields = []
  120. self.objs = []
  121. self.ignore_conflicts = ignore_conflicts
  122. def insert_values(self, fields, objs, raw=False):
  123. self.fields = fields
  124. self.objs = objs
  125. self.raw = raw
  126. class AggregateQuery(Query):
  127. """
  128. Take another query as a parameter to the FROM clause and only select the
  129. elements in the provided list.
  130. """
  131. compiler = 'SQLAggregateCompiler'
  132. def __init__(self, model, inner_query):
  133. self.inner_query = inner_query
  134. super().__init__(model)