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.

where.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """
  2. Code to manage the creation and SQL rendering of 'where' constraints.
  3. """
  4. from django.core.exceptions import EmptyResultSet
  5. from django.utils import tree
  6. from django.utils.functional import cached_property
  7. # Connection types
  8. AND = 'AND'
  9. OR = 'OR'
  10. class WhereNode(tree.Node):
  11. """
  12. An SQL WHERE clause.
  13. The class is tied to the Query class that created it (in order to create
  14. the correct SQL).
  15. A child is usually an expression producing boolean values. Most likely the
  16. expression is a Lookup instance.
  17. However, a child could also be any class with as_sql() and either
  18. relabeled_clone() method or relabel_aliases() and clone() methods and
  19. contains_aggregate attribute.
  20. """
  21. default = AND
  22. resolved = False
  23. conditional = True
  24. def split_having(self, negated=False):
  25. """
  26. Return two possibly None nodes: one for those parts of self that
  27. should be included in the WHERE clause and one for those parts of
  28. self that must be included in the HAVING clause.
  29. """
  30. if not self.contains_aggregate:
  31. return self, None
  32. in_negated = negated ^ self.negated
  33. # If the effective connector is OR and this node contains an aggregate,
  34. # then we need to push the whole branch to HAVING clause.
  35. may_need_split = (
  36. (in_negated and self.connector == AND) or
  37. (not in_negated and self.connector == OR))
  38. if may_need_split and self.contains_aggregate:
  39. return None, self
  40. where_parts = []
  41. having_parts = []
  42. for c in self.children:
  43. if hasattr(c, 'split_having'):
  44. where_part, having_part = c.split_having(in_negated)
  45. if where_part is not None:
  46. where_parts.append(where_part)
  47. if having_part is not None:
  48. having_parts.append(having_part)
  49. elif c.contains_aggregate:
  50. having_parts.append(c)
  51. else:
  52. where_parts.append(c)
  53. having_node = self.__class__(having_parts, self.connector, self.negated) if having_parts else None
  54. where_node = self.__class__(where_parts, self.connector, self.negated) if where_parts else None
  55. return where_node, having_node
  56. def as_sql(self, compiler, connection):
  57. """
  58. Return the SQL version of the where clause and the value to be
  59. substituted in. Return '', [] if this node matches everything,
  60. None, [] if this node is empty, and raise EmptyResultSet if this
  61. node can't match anything.
  62. """
  63. result = []
  64. result_params = []
  65. if self.connector == AND:
  66. full_needed, empty_needed = len(self.children), 1
  67. else:
  68. full_needed, empty_needed = 1, len(self.children)
  69. for child in self.children:
  70. try:
  71. sql, params = compiler.compile(child)
  72. except EmptyResultSet:
  73. empty_needed -= 1
  74. else:
  75. if sql:
  76. result.append(sql)
  77. result_params.extend(params)
  78. else:
  79. full_needed -= 1
  80. # Check if this node matches nothing or everything.
  81. # First check the amount of full nodes and empty nodes
  82. # to make this node empty/full.
  83. # Now, check if this node is full/empty using the
  84. # counts.
  85. if empty_needed == 0:
  86. if self.negated:
  87. return '', []
  88. else:
  89. raise EmptyResultSet
  90. if full_needed == 0:
  91. if self.negated:
  92. raise EmptyResultSet
  93. else:
  94. return '', []
  95. conn = ' %s ' % self.connector
  96. sql_string = conn.join(result)
  97. if sql_string:
  98. if self.negated:
  99. # Some backends (Oracle at least) need parentheses
  100. # around the inner SQL in the negated case, even if the
  101. # inner SQL contains just a single expression.
  102. sql_string = 'NOT (%s)' % sql_string
  103. elif len(result) > 1 or self.resolved:
  104. sql_string = '(%s)' % sql_string
  105. return sql_string, result_params
  106. def get_group_by_cols(self):
  107. cols = []
  108. for child in self.children:
  109. cols.extend(child.get_group_by_cols())
  110. return cols
  111. def get_source_expressions(self):
  112. return self.children[:]
  113. def set_source_expressions(self, children):
  114. assert len(children) == len(self.children)
  115. self.children = children
  116. def relabel_aliases(self, change_map):
  117. """
  118. Relabel the alias values of any children. 'change_map' is a dictionary
  119. mapping old (current) alias values to the new values.
  120. """
  121. for pos, child in enumerate(self.children):
  122. if hasattr(child, 'relabel_aliases'):
  123. # For example another WhereNode
  124. child.relabel_aliases(change_map)
  125. elif hasattr(child, 'relabeled_clone'):
  126. self.children[pos] = child.relabeled_clone(change_map)
  127. def clone(self):
  128. """
  129. Create a clone of the tree. Must only be called on root nodes (nodes
  130. with empty subtree_parents). Childs must be either (Constraint, lookup,
  131. value) tuples, or objects supporting .clone().
  132. """
  133. clone = self.__class__._new_instance(
  134. children=[], connector=self.connector, negated=self.negated)
  135. for child in self.children:
  136. if hasattr(child, 'clone'):
  137. clone.children.append(child.clone())
  138. else:
  139. clone.children.append(child)
  140. return clone
  141. def relabeled_clone(self, change_map):
  142. clone = self.clone()
  143. clone.relabel_aliases(change_map)
  144. return clone
  145. @classmethod
  146. def _contains_aggregate(cls, obj):
  147. if isinstance(obj, tree.Node):
  148. return any(cls._contains_aggregate(c) for c in obj.children)
  149. return obj.contains_aggregate
  150. @cached_property
  151. def contains_aggregate(self):
  152. return self._contains_aggregate(self)
  153. @classmethod
  154. def _contains_over_clause(cls, obj):
  155. if isinstance(obj, tree.Node):
  156. return any(cls._contains_over_clause(c) for c in obj.children)
  157. return obj.contains_over_clause
  158. @cached_property
  159. def contains_over_clause(self):
  160. return self._contains_over_clause(self)
  161. @property
  162. def is_summary(self):
  163. return any(child.is_summary for child in self.children)
  164. def resolve_expression(self, *args, **kwargs):
  165. clone = self.clone()
  166. clone.resolved = True
  167. return clone
  168. class NothingNode:
  169. """A node that matches nothing."""
  170. contains_aggregate = False
  171. def as_sql(self, compiler=None, connection=None):
  172. raise EmptyResultSet
  173. class ExtraWhere:
  174. # The contents are a black box - assume no aggregates are used.
  175. contains_aggregate = False
  176. def __init__(self, sqls, params):
  177. self.sqls = sqls
  178. self.params = params
  179. def as_sql(self, compiler=None, connection=None):
  180. sqls = ["(%s)" % sql for sql in self.sqls]
  181. return " AND ".join(sqls), list(self.params or ())
  182. class SubqueryConstraint:
  183. # Even if aggregates would be used in a subquery, the outer query isn't
  184. # interested about those.
  185. contains_aggregate = False
  186. def __init__(self, alias, columns, targets, query_object):
  187. self.alias = alias
  188. self.columns = columns
  189. self.targets = targets
  190. self.query_object = query_object
  191. def as_sql(self, compiler, connection):
  192. query = self.query_object
  193. query.set_values(self.targets)
  194. query_compiler = query.get_compiler(connection=connection)
  195. return query_compiler.as_subquery_condition(self.alias, self.columns, compiler)