|
2 | 2 | from __future__ import unicode_literals
|
3 | 3 |
|
4 | 4 | import django
|
| 5 | +from django.core.exceptions import EmptyResultSet |
5 | 6 | from django.db import connections
|
6 | 7 | from django.db.models.sql import DeleteQuery, Query, UpdateQuery
|
7 | 8 | from django.db.models.sql.compiler import (
|
8 | 9 | SQLCompiler,
|
9 | 10 | SQLDeleteCompiler,
|
10 | 11 | SQLUpdateCompiler,
|
11 | 12 | )
|
| 13 | +from django.db.models.sql.constants import LOUTER |
| 14 | +from django.db.models.sql.where import ExtraWhere, WhereNode |
12 | 15 |
|
13 | 16 | from .expressions import CTESubqueryResolver
|
| 17 | +from .join import QJoin |
14 | 18 |
|
15 | 19 |
|
16 | 20 | class CTEQuery(Query):
|
@@ -75,9 +79,40 @@ def generate_sql(cls, connection, query, as_sql):
|
75 | 79 | for cte in query._with_ctes:
|
76 | 80 | if django.VERSION > (4, 2):
|
77 | 81 | _ignore_with_col_aliases(cte.query)
|
78 |
| - compiler = cte.query.get_compiler(connection=connection) |
| 82 | + |
| 83 | + alias = query.alias_map.get(cte.name) |
| 84 | + should_elide_empty = ( |
| 85 | + not isinstance(alias, QJoin) or alias.join_type != LOUTER |
| 86 | + ) |
| 87 | + |
| 88 | + if django.VERSION >= (4, 0): |
| 89 | + compiler = cte.query.get_compiler( |
| 90 | + connection=connection, elide_empty=should_elide_empty |
| 91 | + ) |
| 92 | + else: |
| 93 | + compiler = cte.query.get_compiler(connection=connection) |
| 94 | + |
79 | 95 | qn = compiler.quote_name_unless_alias
|
80 |
| - cte_sql, cte_params = compiler.as_sql() |
| 96 | + try: |
| 97 | + cte_sql, cte_params = compiler.as_sql() |
| 98 | + except EmptyResultSet: |
| 99 | + if django.VERSION < (4, 0) and not should_elide_empty: |
| 100 | + # elide_empty is not available prior to Django 4.0. The |
| 101 | + # below behavior emulates the logic of it, rebuilding |
| 102 | + # the CTE query with a WHERE clause that is always false |
| 103 | + # but that the SqlCompiler cannot optimize away. This is |
| 104 | + # only required for left outer joins, as standard inner |
| 105 | + # joins should be optimized and raise the EmptyResultSet |
| 106 | + query = cte.query.copy() |
| 107 | + query.where = WhereNode([ExtraWhere(["1 = 0"], [])]) |
| 108 | + compiler = query.get_compiler(connection=connection) |
| 109 | + cte_sql, cte_params = compiler.as_sql() |
| 110 | + else: |
| 111 | + # If the CTE raises an EmptyResultSet the SqlCompiler still |
| 112 | + # needs to know the information about this base compiler |
| 113 | + # like, col_count and klass_info. |
| 114 | + as_sql() |
| 115 | + raise |
81 | 116 | template = cls.get_cte_query_template(cte)
|
82 | 117 | ctes.append(template.format(name=qn(cte.name), query=cte_sql))
|
83 | 118 | params.extend(cte_params)
|
|
0 commit comments