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.
 
 
 
 

151 lines
5.5 KiB

  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))