Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/db/models/sql/datastructures.py: 31%
95 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1"""
2Useful auxiliary data structures for query construction. Not useful outside
3the SQL domain.
4"""
5from django.core.exceptions import FullResultSet
6from django.db.models.sql.constants import INNER, LOUTER
9class 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 """
16 def __init__(self, names_pos, path_with_names):
17 self.level = names_pos
18 # The path travelled, this includes the path to the multijoin.
19 self.names_with_path = path_with_names
22class Empty:
23 pass
26class Join:
27 """
28 Used by sql.Query and sql.SQLCompiler to generate JOIN clauses into the
29 FROM entry. For example, the SQL generated could be
30 LEFT OUTER JOIN "sometable" T1
31 ON ("othertable"."sometable_id" = "sometable"."id")
33 This class is primarily used in Query.alias_map. All entries in alias_map
34 must be Join compatible by providing the following attributes and methods:
35 - table_name (string)
36 - table_alias (possible alias for the table, can be None)
37 - join_type (can be None for those entries that aren't joined from
38 anything)
39 - parent_alias (which table is this join's parent, can be None similarly
40 to join_type)
41 - as_sql()
42 - relabeled_clone()
43 """
45 def __init__(
46 self,
47 table_name,
48 parent_alias,
49 table_alias,
50 join_type,
51 join_field,
52 nullable,
53 filtered_relation=None,
54 ):
55 # Join table
56 self.table_name = table_name
57 self.parent_alias = parent_alias
58 # Note: table_alias is not necessarily known at instantiation time.
59 self.table_alias = table_alias
60 # LOUTER or INNER
61 self.join_type = join_type
62 # A list of 2-tuples to use in the ON clause of the JOIN.
63 # Each 2-tuple will create one join condition in the ON clause.
64 self.join_cols = join_field.get_joining_columns()
65 # Along which field (or ForeignObjectRel in the reverse join case)
66 self.join_field = join_field
67 # Is this join nullabled?
68 self.nullable = nullable
69 self.filtered_relation = filtered_relation
71 def as_sql(self, compiler, connection):
72 """
73 Generate the full
74 LEFT OUTER JOIN sometable ON sometable.somecol = othertable.othercol, params
75 clause for this join.
76 """
77 join_conditions = []
78 params = []
79 qn = compiler.quote_name_unless_alias
80 qn2 = connection.ops.quote_name
82 # Add a join condition for each pair of joining columns.
83 for lhs_col, rhs_col in self.join_cols:
84 join_conditions.append(
85 "%s.%s = %s.%s"
86 % (
87 qn(self.parent_alias),
88 qn2(lhs_col),
89 qn(self.table_alias),
90 qn2(rhs_col),
91 )
92 )
94 # Add a single condition inside parentheses for whatever
95 # get_extra_restriction() returns.
96 extra_cond = self.join_field.get_extra_restriction(
97 self.table_alias, self.parent_alias
98 )
99 if extra_cond:
100 extra_sql, extra_params = compiler.compile(extra_cond)
101 join_conditions.append("(%s)" % extra_sql)
102 params.extend(extra_params)
103 if self.filtered_relation:
104 try:
105 extra_sql, extra_params = compiler.compile(self.filtered_relation)
106 except FullResultSet:
107 pass
108 else:
109 join_conditions.append("(%s)" % extra_sql)
110 params.extend(extra_params)
111 if not join_conditions:
112 # This might be a rel on the other end of an actual declared field.
113 declared_field = getattr(self.join_field, "field", self.join_field)
114 raise ValueError(
115 "Join generated an empty ON clause. %s did not yield either "
116 "joining columns or extra restrictions." % declared_field.__class__
117 )
118 on_clause_sql = " AND ".join(join_conditions)
119 alias_str = (
120 "" if self.table_alias == self.table_name else (" %s" % self.table_alias)
121 )
122 sql = "%s %s%s ON (%s)" % (
123 self.join_type,
124 qn(self.table_name),
125 alias_str,
126 on_clause_sql,
127 )
128 return sql, params
130 def relabeled_clone(self, change_map):
131 new_parent_alias = change_map.get(self.parent_alias, self.parent_alias)
132 new_table_alias = change_map.get(self.table_alias, self.table_alias)
133 if self.filtered_relation is not None:
134 filtered_relation = self.filtered_relation.clone()
135 filtered_relation.path = [
136 change_map.get(p, p) for p in self.filtered_relation.path
137 ]
138 else:
139 filtered_relation = None
140 return self.__class__(
141 self.table_name,
142 new_parent_alias,
143 new_table_alias,
144 self.join_type,
145 self.join_field,
146 self.nullable,
147 filtered_relation=filtered_relation,
148 )
150 @property
151 def identity(self):
152 return (
153 self.__class__,
154 self.table_name,
155 self.parent_alias,
156 self.join_field,
157 self.filtered_relation,
158 )
160 def __eq__(self, other):
161 if not isinstance(other, Join):
162 return NotImplemented
163 return self.identity == other.identity
165 def __hash__(self):
166 return hash(self.identity)
168 def equals(self, other):
169 # Ignore filtered_relation in equality check.
170 return self.identity[:-1] == other.identity[:-1]
172 def demote(self):
173 new = self.relabeled_clone({})
174 new.join_type = INNER
175 return new
177 def promote(self):
178 new = self.relabeled_clone({})
179 new.join_type = LOUTER
180 return new
183class BaseTable:
184 """
185 The BaseTable class is used for base table references in FROM clause. For
186 example, the SQL "foo" in
187 SELECT * FROM "foo" WHERE somecond
188 could be generated by this class.
189 """
191 join_type = None
192 parent_alias = None
193 filtered_relation = None
195 def __init__(self, table_name, alias):
196 self.table_name = table_name
197 self.table_alias = alias
199 def as_sql(self, compiler, connection):
200 alias_str = (
201 "" if self.table_alias == self.table_name else (" %s" % self.table_alias)
202 )
203 base_sql = compiler.quote_name_unless_alias(self.table_name)
204 return base_sql + alias_str, []
206 def relabeled_clone(self, change_map):
207 return self.__class__(
208 self.table_name, change_map.get(self.table_alias, self.table_alias)
209 )
211 @property
212 def identity(self):
213 return self.__class__, self.table_name, self.table_alias
215 def __eq__(self, other):
216 if not isinstance(other, BaseTable):
217 return NotImplemented
218 return self.identity == other.identity
220 def __hash__(self):
221 return hash(self.identity)
223 def equals(self, other):
224 return self.identity == other.identity