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

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 

7 

8 

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

15 

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 

20 

21 

22class Empty: 

23 pass 

24 

25 

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

32 

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

44 

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 

70 

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 

81 

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 ) 

93 

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 

129 

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 ) 

149 

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 ) 

159 

160 def __eq__(self, other): 

161 if not isinstance(other, Join): 

162 return NotImplemented 

163 return self.identity == other.identity 

164 

165 def __hash__(self): 

166 return hash(self.identity) 

167 

168 def equals(self, other): 

169 # Ignore filtered_relation in equality check. 

170 return self.identity[:-1] == other.identity[:-1] 

171 

172 def demote(self): 

173 new = self.relabeled_clone({}) 

174 new.join_type = INNER 

175 return new 

176 

177 def promote(self): 

178 new = self.relabeled_clone({}) 

179 new.join_type = LOUTER 

180 return new 

181 

182 

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

190 

191 join_type = None 

192 parent_alias = None 

193 filtered_relation = None 

194 

195 def __init__(self, table_name, alias): 

196 self.table_name = table_name 

197 self.table_alias = alias 

198 

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, [] 

205 

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 ) 

210 

211 @property 

212 def identity(self): 

213 return self.__class__, self.table_name, self.table_alias 

214 

215 def __eq__(self, other): 

216 if not isinstance(other, BaseTable): 

217 return NotImplemented 

218 return self.identity == other.identity 

219 

220 def __hash__(self): 

221 return hash(self.identity) 

222 

223 def equals(self, other): 

224 return self.identity == other.identity