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.

datastructures.py 5.5 KiB

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