|
1 | 1 | import itertools
|
2 | 2 |
|
| 3 | +from django.core.exceptions import EmptyResultSet |
| 4 | +from django.db import NotSupportedError |
3 | 5 | from django.db.models.fields import AutoFieldMixin
|
4 | 6 | from django.db.models.sql import compiler
|
5 | 7 |
|
|
9 | 11 |
|
10 | 12 | if compat.dj_ge42:
|
11 | 13 | from django.core.exceptions import FullResultSet
|
| 14 | +else: |
| 15 | + |
| 16 | + class FullResultSet(Exception): |
| 17 | + """A database query predicate is matches everything.""" |
| 18 | + |
| 19 | + pass |
| 20 | + |
12 | 21 |
|
13 | 22 | # Max rows you can insert using expression as value.
|
14 | 23 | MAX_ROWS_INSERT_USE_EXPRESSION = 1000
|
@@ -39,26 +48,234 @@ def _add_settings_sql(self, sql, params):
|
39 | 48 | return sql, params
|
40 | 49 |
|
41 | 50 | def _compile_where(self, table):
|
42 |
| - if compat.dj_ge42: |
43 |
| - try: |
44 |
| - where, params = self.compile(self.query.where) |
45 |
| - except FullResultSet: |
46 |
| - where, params = "", () |
47 |
| - else: |
| 51 | + try: |
48 | 52 | where, params = self.compile(self.query.where)
|
| 53 | + except FullResultSet: |
| 54 | + where, params = "", () |
49 | 55 | if where:
|
50 | 56 | where = where.replace(table + ".", "")
|
51 | 57 | else:
|
52 |
| - where = "1" |
| 58 | + where = "TRUE" |
53 | 59 | return where, params
|
54 | 60 |
|
55 | 61 |
|
56 | 62 | class SQLCompiler(ClickhouseMixin, compiler.SQLCompiler):
|
57 |
| - def as_sql(self, *args, **kwargs): |
58 |
| - sql, params = super().as_sql(*args, **kwargs) |
59 |
| - sql, params = self._add_settings_sql(sql, params) |
60 |
| - sql, params = self._add_explain_sql(sql, params) |
61 |
| - return sql, params |
| 63 | + def pre_sql_setup(self, with_col_aliases=False): |
| 64 | + """ |
| 65 | + Do any necessary class setup immediately prior to producing SQL. This |
| 66 | + is for things that can't necessarily be done in __init__ because we |
| 67 | + might not have all the pieces in place at that time. |
| 68 | + """ |
| 69 | + if compat.dj_ge42: |
| 70 | + self.setup_query(with_col_aliases=with_col_aliases) |
| 71 | + ( |
| 72 | + self.where, |
| 73 | + self.having, |
| 74 | + self.qualify, |
| 75 | + ) = self.query.where.split_having_qualify( |
| 76 | + must_group_by=self.query.group_by is not None |
| 77 | + ) |
| 78 | + ( |
| 79 | + self.prewhere, |
| 80 | + prehaving, |
| 81 | + prequalify, |
| 82 | + ) = self.query.prewhere.split_having_qualify( |
| 83 | + must_group_by=self.query.group_by is not None |
| 84 | + ) |
| 85 | + # Check before ClickHouse complain. |
| 86 | + # DB::Exception: Window function is found in PREWHERE in query. (ILLEGAL_AGGREGATION) |
| 87 | + if prequalify: |
| 88 | + raise NotSupportedError( |
| 89 | + "Window function is disallowed in the prewhere clause." |
| 90 | + ) |
| 91 | + else: |
| 92 | + self.setup_query() |
| 93 | + self.where, self.having = self.query.where.split_having() |
| 94 | + self.prewhere, prehaving = self.query.where.split_having() |
| 95 | + # Check before ClickHouse complain. |
| 96 | + # DB::Exception: Aggregate function is found in PREWHERE in query. (ILLEGAL_AGGREGATION) |
| 97 | + if prehaving: |
| 98 | + raise NotSupportedError( |
| 99 | + "Aggregate function is disallowed in the prewhere clause." |
| 100 | + ) |
| 101 | + order_by = self.get_order_by() |
| 102 | + extra_select = self.get_extra_select(order_by, self.select) |
| 103 | + self.has_extra_select = bool(extra_select) |
| 104 | + group_by = self.get_group_by(self.select + extra_select, order_by) |
| 105 | + return extra_select, order_by, group_by |
| 106 | + |
| 107 | + def as_sql(self, with_limits=True, with_col_aliases=False): |
| 108 | + """ |
| 109 | + Create the SQL for this query. Return the SQL string and list of |
| 110 | + parameters. |
| 111 | +
|
| 112 | + If 'with_limits' is False, any limit/offset information is not included |
| 113 | + in the query. |
| 114 | + """ |
| 115 | + refcounts_before = self.query.alias_refcount.copy() |
| 116 | + try: |
| 117 | + combinator = self.query.combinator |
| 118 | + if compat.dj_ge42: |
| 119 | + extra_select, order_by, group_by = self.pre_sql_setup( |
| 120 | + with_col_aliases=with_col_aliases or bool(combinator), |
| 121 | + ) |
| 122 | + else: |
| 123 | + extra_select, order_by, group_by = self.pre_sql_setup() |
| 124 | + # Is a LIMIT/OFFSET clause needed? |
| 125 | + with_limit_offset = with_limits and self.query.is_sliced |
| 126 | + if combinator: |
| 127 | + result, params = self.get_combinator_sql( |
| 128 | + combinator, self.query.combinator_all |
| 129 | + ) |
| 130 | + # Django >= 4.2 have this branch |
| 131 | + elif compat.dj_ge42 and self.qualify: |
| 132 | + result, params = self.get_qualify_sql() |
| 133 | + order_by = None |
| 134 | + else: |
| 135 | + distinct_fields, distinct_params = self.get_distinct() |
| 136 | + # This must come after 'select', 'ordering', and 'distinct' |
| 137 | + # (see docstring of get_from_clause() for details). |
| 138 | + from_, f_params = self.get_from_clause() |
| 139 | + try: |
| 140 | + where, w_params = ( |
| 141 | + self.compile(self.where) if self.where is not None else ("", []) |
| 142 | + ) |
| 143 | + except EmptyResultSet: |
| 144 | + if compat.dj_ge42 and self.elide_empty: |
| 145 | + raise |
| 146 | + # Use a predicate that's always False. |
| 147 | + where, w_params = "FALSE", [] |
| 148 | + except FullResultSet: |
| 149 | + where, w_params = "", [] |
| 150 | + try: |
| 151 | + having, h_params = ( |
| 152 | + self.compile(self.having) |
| 153 | + if self.having is not None |
| 154 | + else ("", []) |
| 155 | + ) |
| 156 | + except FullResultSet: |
| 157 | + having, h_params = "", [] |
| 158 | + # v1.2.0 new feature, support prewhere clause. |
| 159 | + # refer https://clickhouse.com/docs/en/sql-reference/statements/select/prewhere |
| 160 | + try: |
| 161 | + prewhere, p_params = ( |
| 162 | + self.compile(self.prewhere) |
| 163 | + if self.prewhere is not None |
| 164 | + else ("", []) |
| 165 | + ) |
| 166 | + except EmptyResultSet: |
| 167 | + if compat.dj_ge42 and self.elide_empty: |
| 168 | + raise |
| 169 | + # Use a predicate that's always False. |
| 170 | + prewhere, p_params = "FALSE", [] |
| 171 | + except FullResultSet: |
| 172 | + prewhere, p_params = "", [] |
| 173 | + result = ["SELECT"] |
| 174 | + params = [] |
| 175 | + |
| 176 | + if self.query.distinct: |
| 177 | + distinct_result, distinct_params = self.connection.ops.distinct_sql( |
| 178 | + distinct_fields, |
| 179 | + distinct_params, |
| 180 | + ) |
| 181 | + result += distinct_result |
| 182 | + params += distinct_params |
| 183 | + |
| 184 | + out_cols = [] |
| 185 | + for _, (s_sql, s_params), alias in self.select + extra_select: |
| 186 | + if alias: |
| 187 | + s_sql = "%s AS %s" % ( |
| 188 | + s_sql, |
| 189 | + self.connection.ops.quote_name(alias), |
| 190 | + ) |
| 191 | + params.extend(s_params) |
| 192 | + out_cols.append(s_sql) |
| 193 | + |
| 194 | + result += [", ".join(out_cols)] |
| 195 | + if from_: |
| 196 | + result += ["FROM", *from_] |
| 197 | + params.extend(f_params) |
| 198 | + |
| 199 | + if prewhere: |
| 200 | + result.append("PREWHERE %s" % prewhere) |
| 201 | + params.extend(p_params) |
| 202 | + |
| 203 | + if where: |
| 204 | + result.append("WHERE %s" % where) |
| 205 | + params.extend(w_params) |
| 206 | + |
| 207 | + grouping = [] |
| 208 | + for g_sql, g_params in group_by: |
| 209 | + grouping.append(g_sql) |
| 210 | + params.extend(g_params) |
| 211 | + if grouping: |
| 212 | + if distinct_fields: |
| 213 | + raise NotImplementedError( |
| 214 | + "annotate() + distinct(fields) is not implemented." |
| 215 | + ) |
| 216 | + result.append("GROUP BY %s" % ", ".join(grouping)) |
| 217 | + if self._meta_ordering: |
| 218 | + order_by = None |
| 219 | + if having: |
| 220 | + result.append("HAVING %s" % having) |
| 221 | + params.extend(h_params) |
| 222 | + |
| 223 | + if order_by: |
| 224 | + ordering = [] |
| 225 | + for _, (o_sql, o_params, _) in order_by: |
| 226 | + ordering.append(o_sql) |
| 227 | + params.extend(o_params) |
| 228 | + order_by_sql = "ORDER BY %s" % ", ".join(ordering) |
| 229 | + result.append(order_by_sql) |
| 230 | + |
| 231 | + if with_limit_offset: |
| 232 | + result.append( |
| 233 | + self.connection.ops.limit_offset_sql( |
| 234 | + self.query.low_mark, self.query.high_mark |
| 235 | + ) |
| 236 | + ) |
| 237 | + |
| 238 | + if self.query.subquery and extra_select: |
| 239 | + # If the query is used as a subquery, the extra selects would |
| 240 | + # result in more columns than the left-hand side expression is |
| 241 | + # expecting. This can happen when a subquery uses a combination |
| 242 | + # of order_by() and distinct(), forcing the ordering expressions |
| 243 | + # to be selected as well. Wrap the query in another subquery |
| 244 | + # to exclude extraneous selects. |
| 245 | + sub_selects = [] |
| 246 | + sub_params = [] |
| 247 | + for index, (select, _, alias) in enumerate(self.select, start=1): |
| 248 | + if alias: |
| 249 | + sub_selects.append( |
| 250 | + "%s.%s" |
| 251 | + % ( |
| 252 | + self.connection.ops.quote_name("subquery"), |
| 253 | + self.connection.ops.quote_name(alias), |
| 254 | + ) |
| 255 | + ) |
| 256 | + else: |
| 257 | + select_clone = select.relabeled_clone( |
| 258 | + {select.alias: "subquery"} |
| 259 | + ) |
| 260 | + subselect, subparams = select_clone.as_sql( |
| 261 | + self, self.connection |
| 262 | + ) |
| 263 | + sub_selects.append(subselect) |
| 264 | + sub_params.extend(subparams) |
| 265 | + sql = "SELECT %s FROM (%s) subquery" % ( |
| 266 | + ", ".join(sub_selects), |
| 267 | + " ".join(result), |
| 268 | + ) |
| 269 | + params = tuple(sub_params + params) |
| 270 | + else: |
| 271 | + sql = " ".join(result) |
| 272 | + params = tuple(params) |
| 273 | + sql, params = self._add_settings_sql(sql, params) |
| 274 | + sql, params = self._add_explain_sql(sql, params) |
| 275 | + return sql, params |
| 276 | + finally: |
| 277 | + # Finally do cleanup - get rid of the joins we created above. |
| 278 | + self.query.reset_refcounts(refcounts_before) |
62 | 279 |
|
63 | 280 |
|
64 | 281 | class SQLInsertCompiler(compiler.SQLInsertCompiler):
|
|
0 commit comments