datastructures.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """
  2. Useful auxiliary data structures for query construction. Not useful outside
  3. the SQL domain.
  4. """
  5. from django.db.models.sql.constants import INNER, LOUTER
  6. class MultiJoin(Exception):
  7. """
  8. Used by join construction code to indicate the point at which a
  9. multi-valued join was attempted (if the caller wants to treat that
  10. exceptionally).
  11. """
  12. def __init__(self, names_pos, path_with_names):
  13. self.level = names_pos
  14. # The path travelled, this includes the path to the multijoin.
  15. self.names_with_path = path_with_names
  16. class Empty:
  17. pass
  18. class Join:
  19. """
  20. Used by sql.Query and sql.SQLCompiler to generate JOIN clauses into the
  21. FROM entry. For example, the SQL generated could be
  22. LEFT OUTER JOIN "sometable" T1 ON ("othertable"."sometable_id" = "sometable"."id")
  23. This class is primarily used in Query.alias_map. All entries in alias_map
  24. must be Join compatible by providing the following attributes and methods:
  25. - table_name (string)
  26. - table_alias (possible alias for the table, can be None)
  27. - join_type (can be None for those entries that aren't joined from
  28. anything)
  29. - parent_alias (which table is this join's parent, can be None similarly
  30. to join_type)
  31. - as_sql()
  32. - relabeled_clone()
  33. """
  34. def __init__(self, table_name, parent_alias, table_alias, join_type,
  35. join_field, nullable, filtered_relation=None):
  36. # Join table
  37. self.table_name = table_name
  38. self.parent_alias = parent_alias
  39. # Note: table_alias is not necessarily known at instantiation time.
  40. self.table_alias = table_alias
  41. # LOUTER or INNER
  42. self.join_type = join_type
  43. # A list of 2-tuples to use in the ON clause of the JOIN.
  44. # Each 2-tuple will create one join condition in the ON clause.
  45. self.join_cols = join_field.get_joining_columns()
  46. # Along which field (or ForeignObjectRel in the reverse join case)
  47. self.join_field = join_field
  48. # Is this join nullabled?
  49. self.nullable = nullable
  50. self.filtered_relation = filtered_relation
  51. def as_sql(self, compiler, connection):
  52. """
  53. Generate the full
  54. LEFT OUTER JOIN sometable ON sometable.somecol = othertable.othercol, params
  55. clause for this join.
  56. """
  57. join_conditions = []
  58. params = []
  59. qn = compiler.quote_name_unless_alias
  60. qn2 = connection.ops.quote_name
  61. # Add a join condition for each pair of joining columns.
  62. for lhs_col, rhs_col in self.join_cols:
  63. join_conditions.append('%s.%s = %s.%s' % (
  64. qn(self.parent_alias),
  65. qn2(lhs_col),
  66. qn(self.table_alias),
  67. qn2(rhs_col),
  68. ))
  69. # Add a single condition inside parentheses for whatever
  70. # get_extra_restriction() returns.
  71. extra_cond = self.join_field.get_extra_restriction(
  72. compiler.query.where_class, self.table_alias, self.parent_alias)
  73. if extra_cond:
  74. extra_sql, extra_params = compiler.compile(extra_cond)
  75. join_conditions.append('(%s)' % extra_sql)
  76. params.extend(extra_params)
  77. if self.filtered_relation:
  78. extra_sql, extra_params = compiler.compile(self.filtered_relation)
  79. if extra_sql:
  80. join_conditions.append('(%s)' % extra_sql)
  81. params.extend(extra_params)
  82. if not join_conditions:
  83. # This might be a rel on the other end of an actual declared field.
  84. declared_field = getattr(self.join_field, 'field', self.join_field)
  85. raise ValueError(
  86. "Join generated an empty ON clause. %s did not yield either "
  87. "joining columns or extra restrictions." % declared_field.__class__
  88. )
  89. on_clause_sql = ' AND '.join(join_conditions)
  90. alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
  91. sql = '%s %s%s ON (%s)' % (self.join_type, qn(self.table_name), alias_str, on_clause_sql)
  92. return sql, params
  93. def relabeled_clone(self, change_map):
  94. new_parent_alias = change_map.get(self.parent_alias, self.parent_alias)
  95. new_table_alias = change_map.get(self.table_alias, self.table_alias)
  96. if self.filtered_relation is not None:
  97. filtered_relation = self.filtered_relation.clone()
  98. filtered_relation.path = [change_map.get(p, p) for p in self.filtered_relation.path]
  99. else:
  100. filtered_relation = None
  101. return self.__class__(
  102. self.table_name, new_parent_alias, new_table_alias, self.join_type,
  103. self.join_field, self.nullable, filtered_relation=filtered_relation,
  104. )
  105. @property
  106. def identity(self):
  107. return (
  108. self.__class__,
  109. self.table_name,
  110. self.parent_alias,
  111. self.join_field,
  112. self.filtered_relation,
  113. )
  114. def __eq__(self, other):
  115. if not isinstance(other, Join):
  116. return NotImplemented
  117. return self.identity == other.identity
  118. def __hash__(self):
  119. return hash(self.identity)
  120. def equals(self, other, with_filtered_relation):
  121. if with_filtered_relation:
  122. return self == other
  123. return self.identity[:-1] == other.identity[:-1]
  124. def demote(self):
  125. new = self.relabeled_clone({})
  126. new.join_type = INNER
  127. return new
  128. def promote(self):
  129. new = self.relabeled_clone({})
  130. new.join_type = LOUTER
  131. return new
  132. class BaseTable:
  133. """
  134. The BaseTable class is used for base table references in FROM clause. For
  135. example, the SQL "foo" in
  136. SELECT * FROM "foo" WHERE somecond
  137. could be generated by this class.
  138. """
  139. join_type = None
  140. parent_alias = None
  141. filtered_relation = None
  142. def __init__(self, table_name, alias):
  143. self.table_name = table_name
  144. self.table_alias = alias
  145. def as_sql(self, compiler, connection):
  146. alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
  147. base_sql = compiler.quote_name_unless_alias(self.table_name)
  148. return base_sql + alias_str, []
  149. def relabeled_clone(self, change_map):
  150. return self.__class__(self.table_name, change_map.get(self.table_alias, self.table_alias))
  151. @property
  152. def identity(self):
  153. return self.__class__, self.table_name, self.table_alias
  154. def __eq__(self, other):
  155. if not isinstance(other, BaseTable):
  156. return NotImplemented
  157. return self.identity == other.identity
  158. def __hash__(self):
  159. return hash(self.identity)
  160. def equals(self, other, with_filtered_relation):
  161. return self.identity == other.identity