Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/db/models/sql/datastructures.py: 74%

92 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2Useful auxiliary data structures for query construction. Not useful outside 

3the SQL domain. 

4""" 

5from django.db.models.sql.constants import INNER, LOUTER 

6 

7 

8class 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 

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 

20 

21class Empty: 

22 pass 

23 

24 

25class Join: 

26 """ 

27 Used by sql.Query and sql.SQLCompiler to generate JOIN clauses into the 

28 FROM entry. For example, the SQL generated could be 

29 LEFT OUTER JOIN "sometable" T1 

30 ON ("othertable"."sometable_id" = "sometable"."id") 

31 

32 This class is primarily used in Query.alias_map. All entries in alias_map 

33 must be Join compatible by providing the following attributes and methods: 

34 - table_name (string) 

35 - table_alias (possible alias for the table, can be None) 

36 - join_type (can be None for those entries that aren't joined from 

37 anything) 

38 - parent_alias (which table is this join's parent, can be None similarly 

39 to join_type) 

40 - as_sql() 

41 - relabeled_clone() 

42 """ 

43 

44 def __init__( 

45 self, 

46 table_name, 

47 parent_alias, 

48 table_alias, 

49 join_type, 

50 join_field, 

51 nullable, 

52 filtered_relation=None, 

53 ): 

54 # Join table 

55 self.table_name = table_name 

56 self.parent_alias = parent_alias 

57 # Note: table_alias is not necessarily known at instantiation time. 

58 self.table_alias = table_alias 

59 # LOUTER or INNER 

60 self.join_type = join_type 

61 # A list of 2-tuples to use in the ON clause of the JOIN. 

62 # Each 2-tuple will create one join condition in the ON clause. 

63 self.join_cols = join_field.get_joining_columns() 

64 # Along which field (or ForeignObjectRel in the reverse join case) 

65 self.join_field = join_field 

66 # Is this join nullabled? 

67 self.nullable = nullable 

68 self.filtered_relation = filtered_relation 

69 

70 def as_sql(self, compiler, connection): 

71 """ 

72 Generate the full 

73 LEFT OUTER JOIN sometable ON sometable.somecol = othertable.othercol, params 

74 clause for this join. 

75 """ 

76 join_conditions = [] 

77 params = [] 

78 qn = compiler.quote_name_unless_alias 

79 qn2 = connection.ops.quote_name 

80 

81 # Add a join condition for each pair of joining columns. 

82 for lhs_col, rhs_col in self.join_cols: 

83 join_conditions.append( 

84 "%s.%s = %s.%s" 

85 % ( 

86 qn(self.parent_alias), 

87 qn2(lhs_col), 

88 qn(self.table_alias), 

89 qn2(rhs_col), 

90 ) 

91 ) 

92 

93 # Add a single condition inside parentheses for whatever 

94 # get_extra_restriction() returns. 

95 extra_cond = self.join_field.get_extra_restriction( 

96 self.table_alias, self.parent_alias 

97 ) 

98 if extra_cond: 98 ↛ 99line 98 didn't jump to line 99, because the condition on line 98 was never true

99 extra_sql, extra_params = compiler.compile(extra_cond) 

100 join_conditions.append("(%s)" % extra_sql) 

101 params.extend(extra_params) 

102 if self.filtered_relation: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true

103 extra_sql, extra_params = compiler.compile(self.filtered_relation) 

104 if extra_sql: 

105 join_conditions.append("(%s)" % extra_sql) 

106 params.extend(extra_params) 

107 if not join_conditions: 107 ↛ 109line 107 didn't jump to line 109, because the condition on line 107 was never true

108 # This might be a rel on the other end of an actual declared field. 

109 declared_field = getattr(self.join_field, "field", self.join_field) 

110 raise ValueError( 

111 "Join generated an empty ON clause. %s did not yield either " 

112 "joining columns or extra restrictions." % declared_field.__class__ 

113 ) 

114 on_clause_sql = " AND ".join(join_conditions) 

115 alias_str = ( 

116 "" if self.table_alias == self.table_name else (" %s" % self.table_alias) 

117 ) 

118 sql = "%s %s%s ON (%s)" % ( 

119 self.join_type, 

120 qn(self.table_name), 

121 alias_str, 

122 on_clause_sql, 

123 ) 

124 return sql, params 

125 

126 def relabeled_clone(self, change_map): 

127 new_parent_alias = change_map.get(self.parent_alias, self.parent_alias) 

128 new_table_alias = change_map.get(self.table_alias, self.table_alias) 

129 if self.filtered_relation is not None: 129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true

130 filtered_relation = self.filtered_relation.clone() 

131 filtered_relation.path = [ 

132 change_map.get(p, p) for p in self.filtered_relation.path 

133 ] 

134 else: 

135 filtered_relation = None 

136 return self.__class__( 

137 self.table_name, 

138 new_parent_alias, 

139 new_table_alias, 

140 self.join_type, 

141 self.join_field, 

142 self.nullable, 

143 filtered_relation=filtered_relation, 

144 ) 

145 

146 @property 

147 def identity(self): 

148 return ( 

149 self.__class__, 

150 self.table_name, 

151 self.parent_alias, 

152 self.join_field, 

153 self.filtered_relation, 

154 ) 

155 

156 def __eq__(self, other): 

157 if not isinstance(other, Join): 

158 return NotImplemented 

159 return self.identity == other.identity 

160 

161 def __hash__(self): 

162 return hash(self.identity) 

163 

164 def equals(self, other): 

165 # Ignore filtered_relation in equality check. 

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

167 

168 def demote(self): 

169 new = self.relabeled_clone({}) 

170 new.join_type = INNER 

171 return new 

172 

173 def promote(self): 

174 new = self.relabeled_clone({}) 

175 new.join_type = LOUTER 

176 return new 

177 

178 

179class 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 

187 join_type = None 

188 parent_alias = None 

189 filtered_relation = None 

190 

191 def __init__(self, table_name, alias): 

192 self.table_name = table_name 

193 self.table_alias = alias 

194 

195 def as_sql(self, compiler, connection): 

196 alias_str = ( 

197 "" if self.table_alias == self.table_name else (" %s" % self.table_alias) 

198 ) 

199 base_sql = compiler.quote_name_unless_alias(self.table_name) 

200 return base_sql + alias_str, [] 

201 

202 def relabeled_clone(self, change_map): 

203 return self.__class__( 

204 self.table_name, change_map.get(self.table_alias, self.table_alias) 

205 ) 

206 

207 @property 

208 def identity(self): 

209 return self.__class__, self.table_name, self.table_alias 

210 

211 def __eq__(self, other): 

212 if not isinstance(other, BaseTable): 212 ↛ 214line 212 didn't jump to line 214, because the condition on line 212 was never false

213 return NotImplemented 

214 return self.identity == other.identity 

215 

216 def __hash__(self): 

217 return hash(self.identity) 

218 

219 def equals(self, other): 

220 return self.identity == other.identity