datastructures.py 8.1 KB

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