Refs #28333 -- Explicitly ordered outer qualify query on window filtering.

While most backends will propagate derived table ordering as long as
the outer query doesn't perform additional processing the SQL specs
doesn't explicitly state the ordering must be maintained.
This commit is contained in:
Simon Charette 2022-09-15 13:27:32 -04:00 committed by GitHub
parent a69b0e9cfe
commit 3ba7f2e906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 42 additions and 14 deletions

View File

@ -607,24 +607,41 @@ class SQLCompiler:
select = { select = {
expr: alias for expr, _, alias in self.get_select(with_col_aliases=True)[0] expr: alias for expr, _, alias in self.get_select(with_col_aliases=True)[0]
} }
select_aliases = set(select.values())
qual_aliases = set() qual_aliases = set()
replacements = {} replacements = {}
expressions = list(self.qualify.leaves())
while expressions: def collect_replacements(expressions):
expr = expressions.pop() while expressions:
if select_alias := (select.get(expr) or replacements.get(expr)): expr = expressions.pop()
replacements[expr] = select_alias if expr in replacements:
elif isinstance(expr, Lookup): continue
expressions.extend(expr.get_source_expressions()) elif select_alias := select.get(expr):
else: replacements[expr] = select_alias
num_qual_alias = len(qual_aliases) elif isinstance(expr, Lookup):
select_alias = f"qual{num_qual_alias}" expressions.extend(expr.get_source_expressions())
qual_aliases.add(select_alias) elif isinstance(expr, Ref):
inner_query.add_annotation(expr, select_alias) if expr.refs not in select_aliases:
replacements[expr] = select_alias expressions.extend(expr.get_source_expressions())
else:
num_qual_alias = len(qual_aliases)
select_alias = f"qual{num_qual_alias}"
qual_aliases.add(select_alias)
inner_query.add_annotation(expr, select_alias)
replacements[expr] = select_alias
collect_replacements(list(self.qualify.leaves()))
self.qualify = self.qualify.replace_expressions( self.qualify = self.qualify.replace_expressions(
{expr: Ref(alias, expr) for expr, alias in replacements.items()} {expr: Ref(alias, expr) for expr, alias in replacements.items()}
) )
order_by = []
for order_by_expr, *_ in self.get_order_by():
collect_replacements(order_by_expr.get_source_expressions())
order_by.append(
order_by_expr.replace_expressions(
{expr: Ref(alias, expr) for expr, alias in replacements.items()}
)
)
inner_query_compiler = inner_query.get_compiler( inner_query_compiler = inner_query.get_compiler(
self.using, elide_empty=self.elide_empty self.using, elide_empty=self.elide_empty
) )
@ -657,7 +674,18 @@ class SQLCompiler:
")", ")",
self.connection.ops.quote_name("qualify_mask"), self.connection.ops.quote_name("qualify_mask"),
] ]
return result, list(inner_params) + qualify_params params = list(inner_params) + qualify_params
# As the SQL spec is unclear on whether or not derived tables
# ordering must propagate it has to be explicitly repeated on the
# outer-most query to ensure it's preserved.
if order_by:
ordering_sqls = []
for ordering in order_by:
ordering_sql, ordering_params = self.compile(ordering)
ordering_sqls.append(ordering_sql)
params.extend(ordering_params)
result.extend(["ORDER BY", ", ".join(ordering_sqls)])
return result, params
def as_sql(self, with_limits=True, with_col_aliases=False): def as_sql(self, with_limits=True, with_col_aliases=False):
""" """