From 9b39dac84e82bf473216939e50b8836170f01d23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 29 Jun 2023 15:02:29 +0200 Subject: Merging upstream version 16.7.3. Signed-off-by: Daniel Baumann --- docs/sqlglot/planner.html | 1668 +++++++++++++++++++++++---------------------- 1 file changed, 864 insertions(+), 804 deletions(-) (limited to 'docs/sqlglot/planner.html') diff --git a/docs/sqlglot/planner.html b/docs/sqlglot/planner.html index d1eab9b..275b324 100644 --- a/docs/sqlglot/planner.html +++ b/docs/sqlglot/planner.html @@ -284,324 +284,344 @@ 91 A Step DAG corresponding to `expression`. 92 """ 93 ctes = ctes or {} - 94 with_ = expression.args.get("with") - 95 - 96 # CTEs break the mold of scope and introduce themselves to all in the context. - 97 if with_: - 98 ctes = ctes.copy() - 99 for cte in with_.expressions: -100 step = Step.from_expression(cte.this, ctes) -101 step.name = cte.alias -102 ctes[step.name] = step # type: ignore -103 -104 from_ = expression.args.get("from") -105 -106 if isinstance(expression, exp.Select) and from_: -107 step = Scan.from_expression(from_.this, ctes) -108 elif isinstance(expression, exp.Union): -109 step = SetOperation.from_expression(expression, ctes) -110 else: -111 step = Scan() -112 -113 joins = expression.args.get("joins") -114 -115 if joins: -116 join = Join.from_joins(joins, ctes) -117 join.name = step.name -118 join.add_dependency(step) -119 step = join -120 -121 projections = [] # final selects in this chain of steps representing a select -122 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -123 aggregations = [] -124 next_operand_name = name_sequence("_a_") -125 -126 def extract_agg_operands(expression): -127 for agg in expression.find_all(exp.AggFunc): -128 for operand in agg.unnest_operands(): -129 if isinstance(operand, exp.Column): -130 continue -131 if operand not in operands: -132 operands[operand] = next_operand_name() -133 operand.replace(exp.column(operands[operand], quoted=True)) -134 -135 for e in expression.expressions: -136 if e.find(exp.AggFunc): -137 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -138 aggregations.append(e) -139 extract_agg_operands(e) -140 else: -141 projections.append(e) -142 -143 where = expression.args.get("where") -144 -145 if where: -146 step.condition = where.this -147 -148 group = expression.args.get("group") -149 -150 if group or aggregations: -151 aggregate = Aggregate() -152 aggregate.source = step.name -153 aggregate.name = step.name -154 -155 having = expression.args.get("having") -156 -157 if having: -158 extract_agg_operands(having) -159 aggregate.condition = having.this + 94 expression = expression.unnest() + 95 with_ = expression.args.get("with") + 96 + 97 # CTEs break the mold of scope and introduce themselves to all in the context. + 98 if with_: + 99 ctes = ctes.copy() +100 for cte in with_.expressions: +101 step = Step.from_expression(cte.this, ctes) +102 step.name = cte.alias +103 ctes[step.name] = step # type: ignore +104 +105 from_ = expression.args.get("from") +106 +107 if isinstance(expression, exp.Select) and from_: +108 step = Scan.from_expression(from_.this, ctes) +109 elif isinstance(expression, exp.Union): +110 step = SetOperation.from_expression(expression, ctes) +111 else: +112 step = Scan() +113 +114 joins = expression.args.get("joins") +115 +116 if joins: +117 join = Join.from_joins(joins, ctes) +118 join.name = step.name +119 join.add_dependency(step) +120 step = join +121 +122 projections = [] # final selects in this chain of steps representing a select +123 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +124 aggregations = set() +125 next_operand_name = name_sequence("_a_") +126 +127 def extract_agg_operands(expression): +128 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +129 if agg_funcs: +130 aggregations.add(expression) +131 for agg in agg_funcs: +132 for operand in agg.unnest_operands(): +133 if isinstance(operand, exp.Column): +134 continue +135 if operand not in operands: +136 operands[operand] = next_operand_name() +137 operand.replace(exp.column(operands[operand], quoted=True)) +138 return bool(agg_funcs) +139 +140 for e in expression.expressions: +141 if e.find(exp.AggFunc): +142 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +143 extract_agg_operands(e) +144 else: +145 projections.append(e) +146 +147 where = expression.args.get("where") +148 +149 if where: +150 step.condition = where.this +151 +152 group = expression.args.get("group") +153 +154 if group or aggregations: +155 aggregate = Aggregate() +156 aggregate.source = step.name +157 aggregate.name = step.name +158 +159 having = expression.args.get("having") 160 -161 aggregate.operands = tuple( -162 alias(operand, alias_) for operand, alias_ in operands.items() -163 ) -164 aggregate.aggregations = aggregations -165 # give aggregates names and replace projections with references to them -166 aggregate.group = { -167 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -168 } -169 for projection in projections: -170 for i, e in aggregate.group.items(): -171 for child, *_ in projection.walk(): -172 if child == e: -173 child.replace(exp.column(i, step.name)) -174 aggregate.add_dependency(step) -175 step = aggregate +161 if having: +162 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +163 aggregate.condition = exp.column("_h", step.name, quoted=True) +164 else: +165 aggregate.condition = having.this +166 +167 aggregate.operands = tuple( +168 alias(operand, alias_) for operand, alias_ in operands.items() +169 ) +170 aggregate.aggregations = list(aggregations) +171 +172 # give aggregates names and replace projections with references to them +173 aggregate.group = { +174 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +175 } 176 -177 order = expression.args.get("order") -178 -179 if order: -180 sort = Sort() -181 sort.name = step.name -182 sort.key = order.expressions -183 sort.add_dependency(step) -184 step = sort -185 -186 step.projections = projections -187 -188 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -189 distinct = Aggregate() -190 distinct.source = step.name -191 distinct.name = step.name -192 distinct.group = { -193 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -194 for e in projections or expression.expressions -195 } -196 distinct.add_dependency(step) -197 step = distinct +177 intermediate: t.Dict[str | exp.Expression, str] = {} +178 for k, v in aggregate.group.items(): +179 intermediate[v] = k +180 if isinstance(v, exp.Column): +181 intermediate[v.alias_or_name] = k +182 +183 for projection in projections: +184 for node, *_ in projection.walk(): +185 name = intermediate.get(node) +186 if name: +187 node.replace(exp.column(name, step.name)) +188 if aggregate.condition: +189 for node, *_ in aggregate.condition.walk(): +190 name = intermediate.get(node) or intermediate.get(node.name) +191 if name: +192 node.replace(exp.column(name, step.name)) +193 +194 aggregate.add_dependency(step) +195 step = aggregate +196 +197 order = expression.args.get("order") 198 -199 limit = expression.args.get("limit") -200 -201 if limit: -202 step.limit = int(limit.text("expression")) -203 -204 return step +199 if order: +200 sort = Sort() +201 sort.name = step.name +202 sort.key = order.expressions +203 sort.add_dependency(step) +204 step = sort 205 -206 def __init__(self) -> None: -207 self.name: t.Optional[str] = None -208 self.dependencies: t.Set[Step] = set() -209 self.dependents: t.Set[Step] = set() -210 self.projections: t.Sequence[exp.Expression] = [] -211 self.limit: float = math.inf -212 self.condition: t.Optional[exp.Expression] = None -213 -214 def add_dependency(self, dependency: Step) -> None: -215 self.dependencies.add(dependency) -216 dependency.dependents.add(self) -217 -218 def __repr__(self) -> str: -219 return self.to_s() +206 step.projections = projections +207 +208 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +209 distinct = Aggregate() +210 distinct.source = step.name +211 distinct.name = step.name +212 distinct.group = { +213 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +214 for e in projections or expression.expressions +215 } +216 distinct.add_dependency(step) +217 step = distinct +218 +219 limit = expression.args.get("limit") 220 -221 def to_s(self, level: int = 0) -> str: -222 indent = " " * level -223 nested = f"{indent} " -224 -225 context = self._to_s(f"{nested} ") -226 -227 if context: -228 context = [f"{nested}Context:"] + context -229 -230 lines = [ -231 f"{indent}- {self.id}", -232 *context, -233 f"{nested}Projections:", -234 ] -235 -236 for expression in self.projections: -237 lines.append(f"{nested} - {expression.sql()}") -238 -239 if self.condition: -240 lines.append(f"{nested}Condition: {self.condition.sql()}") -241 -242 if self.limit is not math.inf: -243 lines.append(f"{nested}Limit: {self.limit}") +221 if limit: +222 step.limit = int(limit.text("expression")) +223 +224 return step +225 +226 def __init__(self) -> None: +227 self.name: t.Optional[str] = None +228 self.dependencies: t.Set[Step] = set() +229 self.dependents: t.Set[Step] = set() +230 self.projections: t.Sequence[exp.Expression] = [] +231 self.limit: float = math.inf +232 self.condition: t.Optional[exp.Expression] = None +233 +234 def add_dependency(self, dependency: Step) -> None: +235 self.dependencies.add(dependency) +236 dependency.dependents.add(self) +237 +238 def __repr__(self) -> str: +239 return self.to_s() +240 +241 def to_s(self, level: int = 0) -> str: +242 indent = " " * level +243 nested = f"{indent} " 244 -245 if self.dependencies: -246 lines.append(f"{nested}Dependencies:") -247 for dependency in self.dependencies: -248 lines.append(" " + dependency.to_s(level + 1)) +245 context = self._to_s(f"{nested} ") +246 +247 if context: +248 context = [f"{nested}Context:"] + context 249 -250 return "\n".join(lines) -251 -252 @property -253 def type_name(self) -> str: -254 return self.__class__.__name__ +250 lines = [ +251 f"{indent}- {self.id}", +252 *context, +253 f"{nested}Projections:", +254 ] 255 -256 @property -257 def id(self) -> str: -258 name = self.name -259 name = f" {name}" if name else "" -260 return f"{self.type_name}:{name} ({id(self)})" +256 for expression in self.projections: +257 lines.append(f"{nested} - {expression.sql()}") +258 +259 if self.condition: +260 lines.append(f"{nested}Condition: {self.condition.sql()}") 261 -262 def _to_s(self, _indent: str) -> t.List[str]: -263 return [] +262 if self.limit is not math.inf: +263 lines.append(f"{nested}Limit: {self.limit}") 264 -265 -266class Scan(Step): -267 @classmethod -268 def from_expression( -269 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None -270 ) -> Step: -271 table = expression -272 alias_ = expression.alias_or_name -273 -274 if isinstance(expression, exp.Subquery): -275 table = expression.this -276 step = Step.from_expression(table, ctes) -277 step.name = alias_ -278 return step -279 -280 step = Scan() -281 step.name = alias_ -282 step.source = expression -283 if ctes and table.name in ctes: -284 step.add_dependency(ctes[table.name]) +265 if self.dependencies: +266 lines.append(f"{nested}Dependencies:") +267 for dependency in self.dependencies: +268 lines.append(" " + dependency.to_s(level + 1)) +269 +270 return "\n".join(lines) +271 +272 @property +273 def type_name(self) -> str: +274 return self.__class__.__name__ +275 +276 @property +277 def id(self) -> str: +278 name = self.name +279 name = f" {name}" if name else "" +280 return f"{self.type_name}:{name} ({id(self)})" +281 +282 def _to_s(self, _indent: str) -> t.List[str]: +283 return [] +284 285 -286 return step -287 -288 def __init__(self) -> None: -289 super().__init__() -290 self.source: t.Optional[exp.Expression] = None -291 -292 def _to_s(self, indent: str) -> t.List[str]: -293 return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore -294 -295 -296class Join(Step): -297 @classmethod -298 def from_joins( -299 cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None -300 ) -> Step: -301 step = Join() -302 -303 for join in joins: -304 source_key, join_key, condition = join_condition(join) -305 step.joins[join.alias_or_name] = { -306 "side": join.side, # type: ignore -307 "join_key": join_key, -308 "source_key": source_key, -309 "condition": condition, -310 } +286class Scan(Step): +287 @classmethod +288 def from_expression( +289 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None +290 ) -> Step: +291 table = expression +292 alias_ = expression.alias_or_name +293 +294 if isinstance(expression, exp.Subquery): +295 table = expression.this +296 step = Step.from_expression(table, ctes) +297 step.name = alias_ +298 return step +299 +300 step = Scan() +301 step.name = alias_ +302 step.source = expression +303 if ctes and table.name in ctes: +304 step.add_dependency(ctes[table.name]) +305 +306 return step +307 +308 def __init__(self) -> None: +309 super().__init__() +310 self.source: t.Optional[exp.Expression] = None 311 -312 step.add_dependency(Scan.from_expression(join.this, ctes)) -313 -314 return step +312 def _to_s(self, indent: str) -> t.List[str]: +313 return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore +314 315 -316 def __init__(self) -> None: -317 super().__init__() -318 self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {} -319 -320 def _to_s(self, indent: str) -> t.List[str]: -321 lines = [] -322 for name, join in self.joins.items(): -323 lines.append(f"{indent}{name}: {join['side']}") -324 if join.get("condition"): -325 lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore -326 return lines -327 -328 -329class Aggregate(Step): -330 def __init__(self) -> None: -331 super().__init__() -332 self.aggregations: t.List[exp.Expression] = [] -333 self.operands: t.Tuple[exp.Expression, ...] = () -334 self.group: t.Dict[str, exp.Expression] = {} -335 self.source: t.Optional[str] = None -336 -337 def _to_s(self, indent: str) -> t.List[str]: -338 lines = [f"{indent}Aggregations:"] +316class Join(Step): +317 @classmethod +318 def from_joins( +319 cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None +320 ) -> Step: +321 step = Join() +322 +323 for join in joins: +324 source_key, join_key, condition = join_condition(join) +325 step.joins[join.alias_or_name] = { +326 "side": join.side, # type: ignore +327 "join_key": join_key, +328 "source_key": source_key, +329 "condition": condition, +330 } +331 +332 step.add_dependency(Scan.from_expression(join.this, ctes)) +333 +334 return step +335 +336 def __init__(self) -> None: +337 super().__init__() +338 self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {} 339 -340 for expression in self.aggregations: -341 lines.append(f"{indent} - {expression.sql()}") -342 -343 if self.group: -344 lines.append(f"{indent}Group:") -345 for expression in self.group.values(): -346 lines.append(f"{indent} - {expression.sql()}") -347 if self.condition: -348 lines.append(f"{indent}Having:") -349 lines.append(f"{indent} - {self.condition.sql()}") -350 if self.operands: -351 lines.append(f"{indent}Operands:") -352 for expression in self.operands: -353 lines.append(f"{indent} - {expression.sql()}") -354 -355 return lines +340 def _to_s(self, indent: str) -> t.List[str]: +341 lines = [] +342 for name, join in self.joins.items(): +343 lines.append(f"{indent}{name}: {join['side']}") +344 if join.get("condition"): +345 lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore +346 return lines +347 +348 +349class Aggregate(Step): +350 def __init__(self) -> None: +351 super().__init__() +352 self.aggregations: t.List[exp.Expression] = [] +353 self.operands: t.Tuple[exp.Expression, ...] = () +354 self.group: t.Dict[str, exp.Expression] = {} +355 self.source: t.Optional[str] = None 356 -357 -358class Sort(Step): -359 def __init__(self) -> None: -360 super().__init__() -361 self.key = None +357 def _to_s(self, indent: str) -> t.List[str]: +358 lines = [f"{indent}Aggregations:"] +359 +360 for expression in self.aggregations: +361 lines.append(f"{indent} - {expression.sql()}") 362 -363 def _to_s(self, indent: str) -> t.List[str]: -364 lines = [f"{indent}Key:"] -365 -366 for expression in self.key: # type: ignore -367 lines.append(f"{indent} - {expression.sql()}") -368 -369 return lines -370 -371 -372class SetOperation(Step): -373 def __init__( -374 self, -375 op: t.Type[exp.Expression], -376 left: str | None, -377 right: str | None, -378 distinct: bool = False, -379 ) -> None: +363 if self.group: +364 lines.append(f"{indent}Group:") +365 for expression in self.group.values(): +366 lines.append(f"{indent} - {expression.sql()}") +367 if self.condition: +368 lines.append(f"{indent}Having:") +369 lines.append(f"{indent} - {self.condition.sql()}") +370 if self.operands: +371 lines.append(f"{indent}Operands:") +372 for expression in self.operands: +373 lines.append(f"{indent} - {expression.sql()}") +374 +375 return lines +376 +377 +378class Sort(Step): +379 def __init__(self) -> None: 380 super().__init__() -381 self.op = op -382 self.left = left -383 self.right = right -384 self.distinct = distinct +381 self.key = None +382 +383 def _to_s(self, indent: str) -> t.List[str]: +384 lines = [f"{indent}Key:"] 385 -386 @classmethod -387 def from_expression( -388 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None -389 ) -> Step: -390 assert isinstance(expression, exp.Union) -391 left = Step.from_expression(expression.left, ctes) -392 right = Step.from_expression(expression.right, ctes) -393 step = cls( -394 op=expression.__class__, -395 left=left.name, -396 right=right.name, -397 distinct=bool(expression.args.get("distinct")), -398 ) -399 step.add_dependency(left) -400 step.add_dependency(right) -401 return step -402 -403 def _to_s(self, indent: str) -> t.List[str]: -404 lines = [] -405 if self.distinct: -406 lines.append(f"{indent}Distinct: {self.distinct}") -407 return lines -408 -409 @property -410 def type_name(self) -> str: -411 return self.op.__name__ +386 for expression in self.key: # type: ignore +387 lines.append(f"{indent} - {expression.sql()}") +388 +389 return lines +390 +391 +392class SetOperation(Step): +393 def __init__( +394 self, +395 op: t.Type[exp.Expression], +396 left: str | None, +397 right: str | None, +398 distinct: bool = False, +399 ) -> None: +400 super().__init__() +401 self.op = op +402 self.left = left +403 self.right = right +404 self.distinct = distinct +405 +406 @classmethod +407 def from_expression( +408 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None +409 ) -> Step: +410 assert isinstance(expression, exp.Union) +411 left = Step.from_expression(expression.left, ctes) +412 right = Step.from_expression(expression.right, ctes) +413 step = cls( +414 op=expression.__class__, +415 left=left.name, +416 right=right.name, +417 distinct=bool(expression.args.get("distinct")), +418 ) +419 step.add_dependency(left) +420 step.add_dependency(right) +421 return step +422 +423 def _to_s(self, indent: str) -> t.List[str]: +424 lines = [] +425 if self.distinct: +426 lines.append(f"{indent}Distinct: {self.distinct}") +427 return lines +428 +429 @property +430 def type_name(self) -> str: +431 return self.op.__name__ @@ -779,176 +799,196 @@ 92 A Step DAG corresponding to `expression`. 93 """ 94 ctes = ctes or {} - 95 with_ = expression.args.get("with") - 96 - 97 # CTEs break the mold of scope and introduce themselves to all in the context. - 98 if with_: - 99 ctes = ctes.copy() -100 for cte in with_.expressions: -101 step = Step.from_expression(cte.this, ctes) -102 step.name = cte.alias -103 ctes[step.name] = step # type: ignore -104 -105 from_ = expression.args.get("from") -106 -107 if isinstance(expression, exp.Select) and from_: -108 step = Scan.from_expression(from_.this, ctes) -109 elif isinstance(expression, exp.Union): -110 step = SetOperation.from_expression(expression, ctes) -111 else: -112 step = Scan() -113 -114 joins = expression.args.get("joins") -115 -116 if joins: -117 join = Join.from_joins(joins, ctes) -118 join.name = step.name -119 join.add_dependency(step) -120 step = join -121 -122 projections = [] # final selects in this chain of steps representing a select -123 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -124 aggregations = [] -125 next_operand_name = name_sequence("_a_") -126 -127 def extract_agg_operands(expression): -128 for agg in expression.find_all(exp.AggFunc): -129 for operand in agg.unnest_operands(): -130 if isinstance(operand, exp.Column): -131 continue -132 if operand not in operands: -133 operands[operand] = next_operand_name() -134 operand.replace(exp.column(operands[operand], quoted=True)) -135 -136 for e in expression.expressions: -137 if e.find(exp.AggFunc): -138 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -139 aggregations.append(e) -140 extract_agg_operands(e) -141 else: -142 projections.append(e) -143 -144 where = expression.args.get("where") -145 -146 if where: -147 step.condition = where.this -148 -149 group = expression.args.get("group") -150 -151 if group or aggregations: -152 aggregate = Aggregate() -153 aggregate.source = step.name -154 aggregate.name = step.name -155 -156 having = expression.args.get("having") -157 -158 if having: -159 extract_agg_operands(having) -160 aggregate.condition = having.this + 95 expression = expression.unnest() + 96 with_ = expression.args.get("with") + 97 + 98 # CTEs break the mold of scope and introduce themselves to all in the context. + 99 if with_: +100 ctes = ctes.copy() +101 for cte in with_.expressions: +102 step = Step.from_expression(cte.this, ctes) +103 step.name = cte.alias +104 ctes[step.name] = step # type: ignore +105 +106 from_ = expression.args.get("from") +107 +108 if isinstance(expression, exp.Select) and from_: +109 step = Scan.from_expression(from_.this, ctes) +110 elif isinstance(expression, exp.Union): +111 step = SetOperation.from_expression(expression, ctes) +112 else: +113 step = Scan() +114 +115 joins = expression.args.get("joins") +116 +117 if joins: +118 join = Join.from_joins(joins, ctes) +119 join.name = step.name +120 join.add_dependency(step) +121 step = join +122 +123 projections = [] # final selects in this chain of steps representing a select +124 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +125 aggregations = set() +126 next_operand_name = name_sequence("_a_") +127 +128 def extract_agg_operands(expression): +129 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +130 if agg_funcs: +131 aggregations.add(expression) +132 for agg in agg_funcs: +133 for operand in agg.unnest_operands(): +134 if isinstance(operand, exp.Column): +135 continue +136 if operand not in operands: +137 operands[operand] = next_operand_name() +138 operand.replace(exp.column(operands[operand], quoted=True)) +139 return bool(agg_funcs) +140 +141 for e in expression.expressions: +142 if e.find(exp.AggFunc): +143 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +144 extract_agg_operands(e) +145 else: +146 projections.append(e) +147 +148 where = expression.args.get("where") +149 +150 if where: +151 step.condition = where.this +152 +153 group = expression.args.get("group") +154 +155 if group or aggregations: +156 aggregate = Aggregate() +157 aggregate.source = step.name +158 aggregate.name = step.name +159 +160 having = expression.args.get("having") 161 -162 aggregate.operands = tuple( -163 alias(operand, alias_) for operand, alias_ in operands.items() -164 ) -165 aggregate.aggregations = aggregations -166 # give aggregates names and replace projections with references to them -167 aggregate.group = { -168 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -169 } -170 for projection in projections: -171 for i, e in aggregate.group.items(): -172 for child, *_ in projection.walk(): -173 if child == e: -174 child.replace(exp.column(i, step.name)) -175 aggregate.add_dependency(step) -176 step = aggregate +162 if having: +163 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +164 aggregate.condition = exp.column("_h", step.name, quoted=True) +165 else: +166 aggregate.condition = having.this +167 +168 aggregate.operands = tuple( +169 alias(operand, alias_) for operand, alias_ in operands.items() +170 ) +171 aggregate.aggregations = list(aggregations) +172 +173 # give aggregates names and replace projections with references to them +174 aggregate.group = { +175 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +176 } 177 -178 order = expression.args.get("order") -179 -180 if order: -181 sort = Sort() -182 sort.name = step.name -183 sort.key = order.expressions -184 sort.add_dependency(step) -185 step = sort -186 -187 step.projections = projections -188 -189 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -190 distinct = Aggregate() -191 distinct.source = step.name -192 distinct.name = step.name -193 distinct.group = { -194 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -195 for e in projections or expression.expressions -196 } -197 distinct.add_dependency(step) -198 step = distinct +178 intermediate: t.Dict[str | exp.Expression, str] = {} +179 for k, v in aggregate.group.items(): +180 intermediate[v] = k +181 if isinstance(v, exp.Column): +182 intermediate[v.alias_or_name] = k +183 +184 for projection in projections: +185 for node, *_ in projection.walk(): +186 name = intermediate.get(node) +187 if name: +188 node.replace(exp.column(name, step.name)) +189 if aggregate.condition: +190 for node, *_ in aggregate.condition.walk(): +191 name = intermediate.get(node) or intermediate.get(node.name) +192 if name: +193 node.replace(exp.column(name, step.name)) +194 +195 aggregate.add_dependency(step) +196 step = aggregate +197 +198 order = expression.args.get("order") 199 -200 limit = expression.args.get("limit") -201 -202 if limit: -203 step.limit = int(limit.text("expression")) -204 -205 return step +200 if order: +201 sort = Sort() +202 sort.name = step.name +203 sort.key = order.expressions +204 sort.add_dependency(step) +205 step = sort 206 -207 def __init__(self) -> None: -208 self.name: t.Optional[str] = None -209 self.dependencies: t.Set[Step] = set() -210 self.dependents: t.Set[Step] = set() -211 self.projections: t.Sequence[exp.Expression] = [] -212 self.limit: float = math.inf -213 self.condition: t.Optional[exp.Expression] = None -214 -215 def add_dependency(self, dependency: Step) -> None: -216 self.dependencies.add(dependency) -217 dependency.dependents.add(self) -218 -219 def __repr__(self) -> str: -220 return self.to_s() +207 step.projections = projections +208 +209 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +210 distinct = Aggregate() +211 distinct.source = step.name +212 distinct.name = step.name +213 distinct.group = { +214 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +215 for e in projections or expression.expressions +216 } +217 distinct.add_dependency(step) +218 step = distinct +219 +220 limit = expression.args.get("limit") 221 -222 def to_s(self, level: int = 0) -> str: -223 indent = " " * level -224 nested = f"{indent} " -225 -226 context = self._to_s(f"{nested} ") -227 -228 if context: -229 context = [f"{nested}Context:"] + context -230 -231 lines = [ -232 f"{indent}- {self.id}", -233 *context, -234 f"{nested}Projections:", -235 ] -236 -237 for expression in self.projections: -238 lines.append(f"{nested} - {expression.sql()}") -239 -240 if self.condition: -241 lines.append(f"{nested}Condition: {self.condition.sql()}") -242 -243 if self.limit is not math.inf: -244 lines.append(f"{nested}Limit: {self.limit}") +222 if limit: +223 step.limit = int(limit.text("expression")) +224 +225 return step +226 +227 def __init__(self) -> None: +228 self.name: t.Optional[str] = None +229 self.dependencies: t.Set[Step] = set() +230 self.dependents: t.Set[Step] = set() +231 self.projections: t.Sequence[exp.Expression] = [] +232 self.limit: float = math.inf +233 self.condition: t.Optional[exp.Expression] = None +234 +235 def add_dependency(self, dependency: Step) -> None: +236 self.dependencies.add(dependency) +237 dependency.dependents.add(self) +238 +239 def __repr__(self) -> str: +240 return self.to_s() +241 +242 def to_s(self, level: int = 0) -> str: +243 indent = " " * level +244 nested = f"{indent} " 245 -246 if self.dependencies: -247 lines.append(f"{nested}Dependencies:") -248 for dependency in self.dependencies: -249 lines.append(" " + dependency.to_s(level + 1)) +246 context = self._to_s(f"{nested} ") +247 +248 if context: +249 context = [f"{nested}Context:"] + context 250 -251 return "\n".join(lines) -252 -253 @property -254 def type_name(self) -> str: -255 return self.__class__.__name__ +251 lines = [ +252 f"{indent}- {self.id}", +253 *context, +254 f"{nested}Projections:", +255 ] 256 -257 @property -258 def id(self) -> str: -259 name = self.name -260 name = f" {name}" if name else "" -261 return f"{self.type_name}:{name} ({id(self)})" +257 for expression in self.projections: +258 lines.append(f"{nested} - {expression.sql()}") +259 +260 if self.condition: +261 lines.append(f"{nested}Condition: {self.condition.sql()}") 262 -263 def _to_s(self, _indent: str) -> t.List[str]: -264 return [] +263 if self.limit is not math.inf: +264 lines.append(f"{nested}Limit: {self.limit}") +265 +266 if self.dependencies: +267 lines.append(f"{nested}Dependencies:") +268 for dependency in self.dependencies: +269 lines.append(" " + dependency.to_s(level + 1)) +270 +271 return "\n".join(lines) +272 +273 @property +274 def type_name(self) -> str: +275 return self.__class__.__name__ +276 +277 @property +278 def id(self) -> str: +279 name = self.name +280 name = f" {name}" if name else "" +281 return f"{self.type_name}:{name} ({id(self)})" +282 +283 def _to_s(self, _indent: str) -> t.List[str]: +284 return [] @@ -1018,117 +1058,137 @@ 92 A Step DAG corresponding to `expression`. 93 """ 94 ctes = ctes or {} - 95 with_ = expression.args.get("with") - 96 - 97 # CTEs break the mold of scope and introduce themselves to all in the context. - 98 if with_: - 99 ctes = ctes.copy() -100 for cte in with_.expressions: -101 step = Step.from_expression(cte.this, ctes) -102 step.name = cte.alias -103 ctes[step.name] = step # type: ignore -104 -105 from_ = expression.args.get("from") -106 -107 if isinstance(expression, exp.Select) and from_: -108 step = Scan.from_expression(from_.this, ctes) -109 elif isinstance(expression, exp.Union): -110 step = SetOperation.from_expression(expression, ctes) -111 else: -112 step = Scan() -113 -114 joins = expression.args.get("joins") -115 -116 if joins: -117 join = Join.from_joins(joins, ctes) -118 join.name = step.name -119 join.add_dependency(step) -120 step = join -121 -122 projections = [] # final selects in this chain of steps representing a select -123 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -124 aggregations = [] -125 next_operand_name = name_sequence("_a_") -126 -127 def extract_agg_operands(expression): -128 for agg in expression.find_all(exp.AggFunc): -129 for operand in agg.unnest_operands(): -130 if isinstance(operand, exp.Column): -131 continue -132 if operand not in operands: -133 operands[operand] = next_operand_name() -134 operand.replace(exp.column(operands[operand], quoted=True)) -135 -136 for e in expression.expressions: -137 if e.find(exp.AggFunc): -138 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -139 aggregations.append(e) -140 extract_agg_operands(e) -141 else: -142 projections.append(e) -143 -144 where = expression.args.get("where") -145 -146 if where: -147 step.condition = where.this -148 -149 group = expression.args.get("group") -150 -151 if group or aggregations: -152 aggregate = Aggregate() -153 aggregate.source = step.name -154 aggregate.name = step.name -155 -156 having = expression.args.get("having") -157 -158 if having: -159 extract_agg_operands(having) -160 aggregate.condition = having.this + 95 expression = expression.unnest() + 96 with_ = expression.args.get("with") + 97 + 98 # CTEs break the mold of scope and introduce themselves to all in the context. + 99 if with_: +100 ctes = ctes.copy() +101 for cte in with_.expressions: +102 step = Step.from_expression(cte.this, ctes) +103 step.name = cte.alias +104 ctes[step.name] = step # type: ignore +105 +106 from_ = expression.args.get("from") +107 +108 if isinstance(expression, exp.Select) and from_: +109 step = Scan.from_expression(from_.this, ctes) +110 elif isinstance(expression, exp.Union): +111 step = SetOperation.from_expression(expression, ctes) +112 else: +113 step = Scan() +114 +115 joins = expression.args.get("joins") +116 +117 if joins: +118 join = Join.from_joins(joins, ctes) +119 join.name = step.name +120 join.add_dependency(step) +121 step = join +122 +123 projections = [] # final selects in this chain of steps representing a select +124 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +125 aggregations = set() +126 next_operand_name = name_sequence("_a_") +127 +128 def extract_agg_operands(expression): +129 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +130 if agg_funcs: +131 aggregations.add(expression) +132 for agg in agg_funcs: +133 for operand in agg.unnest_operands(): +134 if isinstance(operand, exp.Column): +135 continue +136 if operand not in operands: +137 operands[operand] = next_operand_name() +138 operand.replace(exp.column(operands[operand], quoted=True)) +139 return bool(agg_funcs) +140 +141 for e in expression.expressions: +142 if e.find(exp.AggFunc): +143 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +144 extract_agg_operands(e) +145 else: +146 projections.append(e) +147 +148 where = expression.args.get("where") +149 +150 if where: +151 step.condition = where.this +152 +153 group = expression.args.get("group") +154 +155 if group or aggregations: +156 aggregate = Aggregate() +157 aggregate.source = step.name +158 aggregate.name = step.name +159 +160 having = expression.args.get("having") 161 -162 aggregate.operands = tuple( -163 alias(operand, alias_) for operand, alias_ in operands.items() -164 ) -165 aggregate.aggregations = aggregations -166 # give aggregates names and replace projections with references to them -167 aggregate.group = { -168 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -169 } -170 for projection in projections: -171 for i, e in aggregate.group.items(): -172 for child, *_ in projection.walk(): -173 if child == e: -174 child.replace(exp.column(i, step.name)) -175 aggregate.add_dependency(step) -176 step = aggregate +162 if having: +163 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +164 aggregate.condition = exp.column("_h", step.name, quoted=True) +165 else: +166 aggregate.condition = having.this +167 +168 aggregate.operands = tuple( +169 alias(operand, alias_) for operand, alias_ in operands.items() +170 ) +171 aggregate.aggregations = list(aggregations) +172 +173 # give aggregates names and replace projections with references to them +174 aggregate.group = { +175 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +176 } 177 -178 order = expression.args.get("order") -179 -180 if order: -181 sort = Sort() -182 sort.name = step.name -183 sort.key = order.expressions -184 sort.add_dependency(step) -185 step = sort -186 -187 step.projections = projections -188 -189 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -190 distinct = Aggregate() -191 distinct.source = step.name -192 distinct.name = step.name -193 distinct.group = { -194 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -195 for e in projections or expression.expressions -196 } -197 distinct.add_dependency(step) -198 step = distinct +178 intermediate: t.Dict[str | exp.Expression, str] = {} +179 for k, v in aggregate.group.items(): +180 intermediate[v] = k +181 if isinstance(v, exp.Column): +182 intermediate[v.alias_or_name] = k +183 +184 for projection in projections: +185 for node, *_ in projection.walk(): +186 name = intermediate.get(node) +187 if name: +188 node.replace(exp.column(name, step.name)) +189 if aggregate.condition: +190 for node, *_ in aggregate.condition.walk(): +191 name = intermediate.get(node) or intermediate.get(node.name) +192 if name: +193 node.replace(exp.column(name, step.name)) +194 +195 aggregate.add_dependency(step) +196 step = aggregate +197 +198 order = expression.args.get("order") 199 -200 limit = expression.args.get("limit") -201 -202 if limit: -203 step.limit = int(limit.text("expression")) -204 -205 return step +200 if order: +201 sort = Sort() +202 sort.name = step.name +203 sort.key = order.expressions +204 sort.add_dependency(step) +205 step = sort +206 +207 step.projections = projections +208 +209 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +210 distinct = Aggregate() +211 distinct.source = step.name +212 distinct.name = step.name +213 distinct.group = { +214 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +215 for e in projections or expression.expressions +216 } +217 distinct.add_dependency(step) +218 step = distinct +219 +220 limit = expression.args.get("limit") +221 +222 if limit: +223 step.limit = int(limit.text("expression")) +224 +225 return step @@ -1270,9 +1330,9 @@ Projections: -
215    def add_dependency(self, dependency: Step) -> None:
-216        self.dependencies.add(dependency)
-217        dependency.dependents.add(self)
+            
235    def add_dependency(self, dependency: Step) -> None:
+236        self.dependencies.add(dependency)
+237        dependency.dependents.add(self)
 
@@ -1290,36 +1350,36 @@ Projections:
-
222    def to_s(self, level: int = 0) -> str:
-223        indent = "  " * level
-224        nested = f"{indent}    "
-225
-226        context = self._to_s(f"{nested}  ")
-227
-228        if context:
-229            context = [f"{nested}Context:"] + context
-230
-231        lines = [
-232            f"{indent}- {self.id}",
-233            *context,
-234            f"{nested}Projections:",
-235        ]
-236
-237        for expression in self.projections:
-238            lines.append(f"{nested}  - {expression.sql()}")
-239
-240        if self.condition:
-241            lines.append(f"{nested}Condition: {self.condition.sql()}")
-242
-243        if self.limit is not math.inf:
-244            lines.append(f"{nested}Limit: {self.limit}")
+            
242    def to_s(self, level: int = 0) -> str:
+243        indent = "  " * level
+244        nested = f"{indent}    "
 245
-246        if self.dependencies:
-247            lines.append(f"{nested}Dependencies:")
-248            for dependency in self.dependencies:
-249                lines.append("  " + dependency.to_s(level + 1))
+246        context = self._to_s(f"{nested}  ")
+247
+248        if context:
+249            context = [f"{nested}Context:"] + context
 250
-251        return "\n".join(lines)
+251        lines = [
+252            f"{indent}- {self.id}",
+253            *context,
+254            f"{nested}Projections:",
+255        ]
+256
+257        for expression in self.projections:
+258            lines.append(f"{nested}  - {expression.sql()}")
+259
+260        if self.condition:
+261            lines.append(f"{nested}Condition: {self.condition.sql()}")
+262
+263        if self.limit is not math.inf:
+264            lines.append(f"{nested}Limit: {self.limit}")
+265
+266        if self.dependencies:
+267            lines.append(f"{nested}Dependencies:")
+268            for dependency in self.dependencies:
+269                lines.append("  " + dependency.to_s(level + 1))
+270
+271        return "\n".join(lines)
 
@@ -1360,34 +1420,34 @@ Projections:
-
267class Scan(Step):
-268    @classmethod
-269    def from_expression(
-270        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
-271    ) -> Step:
-272        table = expression
-273        alias_ = expression.alias_or_name
-274
-275        if isinstance(expression, exp.Subquery):
-276            table = expression.this
-277            step = Step.from_expression(table, ctes)
-278            step.name = alias_
-279            return step
-280
-281        step = Scan()
-282        step.name = alias_
-283        step.source = expression
-284        if ctes and table.name in ctes:
-285            step.add_dependency(ctes[table.name])
-286
-287        return step
-288
-289    def __init__(self) -> None:
-290        super().__init__()
-291        self.source: t.Optional[exp.Expression] = None
-292
-293    def _to_s(self, indent: str) -> t.List[str]:
-294        return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"]  # type: ignore
+            
287class Scan(Step):
+288    @classmethod
+289    def from_expression(
+290        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
+291    ) -> Step:
+292        table = expression
+293        alias_ = expression.alias_or_name
+294
+295        if isinstance(expression, exp.Subquery):
+296            table = expression.this
+297            step = Step.from_expression(table, ctes)
+298            step.name = alias_
+299            return step
+300
+301        step = Scan()
+302        step.name = alias_
+303        step.source = expression
+304        if ctes and table.name in ctes:
+305            step.add_dependency(ctes[table.name])
+306
+307        return step
+308
+309    def __init__(self) -> None:
+310        super().__init__()
+311        self.source: t.Optional[exp.Expression] = None
+312
+313    def _to_s(self, indent: str) -> t.List[str]:
+314        return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"]  # type: ignore
 
@@ -1405,26 +1465,26 @@ Projections:
-
268    @classmethod
-269    def from_expression(
-270        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
-271    ) -> Step:
-272        table = expression
-273        alias_ = expression.alias_or_name
-274
-275        if isinstance(expression, exp.Subquery):
-276            table = expression.this
-277            step = Step.from_expression(table, ctes)
-278            step.name = alias_
-279            return step
-280
-281        step = Scan()
-282        step.name = alias_
-283        step.source = expression
-284        if ctes and table.name in ctes:
-285            step.add_dependency(ctes[table.name])
-286
-287        return step
+            
288    @classmethod
+289    def from_expression(
+290        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
+291    ) -> Step:
+292        table = expression
+293        alias_ = expression.alias_or_name
+294
+295        if isinstance(expression, exp.Subquery):
+296            table = expression.this
+297            step = Step.from_expression(table, ctes)
+298            step.name = alias_
+299            return step
+300
+301        step = Scan()
+302        step.name = alias_
+303        step.source = expression
+304        if ctes and table.name in ctes:
+305            step.add_dependency(ctes[table.name])
+306
+307        return step
 
@@ -1530,37 +1590,37 @@ Projections:
-
297class Join(Step):
-298    @classmethod
-299    def from_joins(
-300        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
-301    ) -> Step:
-302        step = Join()
-303
-304        for join in joins:
-305            source_key, join_key, condition = join_condition(join)
-306            step.joins[join.alias_or_name] = {
-307                "side": join.side,  # type: ignore
-308                "join_key": join_key,
-309                "source_key": source_key,
-310                "condition": condition,
-311            }
-312
-313            step.add_dependency(Scan.from_expression(join.this, ctes))
-314
-315        return step
-316
-317    def __init__(self) -> None:
-318        super().__init__()
-319        self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {}
-320
-321    def _to_s(self, indent: str) -> t.List[str]:
-322        lines = []
-323        for name, join in self.joins.items():
-324            lines.append(f"{indent}{name}: {join['side']}")
-325            if join.get("condition"):
-326                lines.append(f"{indent}On: {join['condition'].sql()}")  # type: ignore
-327        return lines
+            
317class Join(Step):
+318    @classmethod
+319    def from_joins(
+320        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
+321    ) -> Step:
+322        step = Join()
+323
+324        for join in joins:
+325            source_key, join_key, condition = join_condition(join)
+326            step.joins[join.alias_or_name] = {
+327                "side": join.side,  # type: ignore
+328                "join_key": join_key,
+329                "source_key": source_key,
+330                "condition": condition,
+331            }
+332
+333            step.add_dependency(Scan.from_expression(join.this, ctes))
+334
+335        return step
+336
+337    def __init__(self) -> None:
+338        super().__init__()
+339        self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {}
+340
+341    def _to_s(self, indent: str) -> t.List[str]:
+342        lines = []
+343        for name, join in self.joins.items():
+344            lines.append(f"{indent}{name}: {join['side']}")
+345            if join.get("condition"):
+346                lines.append(f"{indent}On: {join['condition'].sql()}")  # type: ignore
+347        return lines
 
@@ -1578,24 +1638,24 @@ Projections:
-
298    @classmethod
-299    def from_joins(
-300        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
-301    ) -> Step:
-302        step = Join()
-303
-304        for join in joins:
-305            source_key, join_key, condition = join_condition(join)
-306            step.joins[join.alias_or_name] = {
-307                "side": join.side,  # type: ignore
-308                "join_key": join_key,
-309                "source_key": source_key,
-310                "condition": condition,
-311            }
-312
-313            step.add_dependency(Scan.from_expression(join.this, ctes))
-314
-315        return step
+            
318    @classmethod
+319    def from_joins(
+320        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
+321    ) -> Step:
+322        step = Join()
+323
+324        for join in joins:
+325            source_key, join_key, condition = join_condition(join)
+326            step.joins[join.alias_or_name] = {
+327                "side": join.side,  # type: ignore
+328                "join_key": join_key,
+329                "source_key": source_key,
+330                "condition": condition,
+331            }
+332
+333            step.add_dependency(Scan.from_expression(join.this, ctes))
+334
+335        return step
 
@@ -1644,33 +1704,33 @@ Projections:
-
330class Aggregate(Step):
-331    def __init__(self) -> None:
-332        super().__init__()
-333        self.aggregations: t.List[exp.Expression] = []
-334        self.operands: t.Tuple[exp.Expression, ...] = ()
-335        self.group: t.Dict[str, exp.Expression] = {}
-336        self.source: t.Optional[str] = None
-337
-338    def _to_s(self, indent: str) -> t.List[str]:
-339        lines = [f"{indent}Aggregations:"]
-340
-341        for expression in self.aggregations:
-342            lines.append(f"{indent}  - {expression.sql()}")
-343
-344        if self.group:
-345            lines.append(f"{indent}Group:")
-346            for expression in self.group.values():
-347                lines.append(f"{indent}  - {expression.sql()}")
-348        if self.condition:
-349            lines.append(f"{indent}Having:")
-350            lines.append(f"{indent}  - {self.condition.sql()}")
-351        if self.operands:
-352            lines.append(f"{indent}Operands:")
-353            for expression in self.operands:
-354                lines.append(f"{indent}  - {expression.sql()}")
-355
-356        return lines
+            
350class Aggregate(Step):
+351    def __init__(self) -> None:
+352        super().__init__()
+353        self.aggregations: t.List[exp.Expression] = []
+354        self.operands: t.Tuple[exp.Expression, ...] = ()
+355        self.group: t.Dict[str, exp.Expression] = {}
+356        self.source: t.Optional[str] = None
+357
+358    def _to_s(self, indent: str) -> t.List[str]:
+359        lines = [f"{indent}Aggregations:"]
+360
+361        for expression in self.aggregations:
+362            lines.append(f"{indent}  - {expression.sql()}")
+363
+364        if self.group:
+365            lines.append(f"{indent}Group:")
+366            for expression in self.group.values():
+367                lines.append(f"{indent}  - {expression.sql()}")
+368        if self.condition:
+369            lines.append(f"{indent}Having:")
+370            lines.append(f"{indent}  - {self.condition.sql()}")
+371        if self.operands:
+372            lines.append(f"{indent}Operands:")
+373            for expression in self.operands:
+374                lines.append(f"{indent}  - {expression.sql()}")
+375
+376        return lines
 
@@ -1751,18 +1811,18 @@ Projections:
-
359class Sort(Step):
-360    def __init__(self) -> None:
-361        super().__init__()
-362        self.key = None
-363
-364    def _to_s(self, indent: str) -> t.List[str]:
-365        lines = [f"{indent}Key:"]
-366
-367        for expression in self.key:  # type: ignore
-368            lines.append(f"{indent}  - {expression.sql()}")
-369
-370        return lines
+            
379class Sort(Step):
+380    def __init__(self) -> None:
+381        super().__init__()
+382        self.key = None
+383
+384    def _to_s(self, indent: str) -> t.List[str]:
+385        lines = [f"{indent}Key:"]
+386
+387        for expression in self.key:  # type: ignore
+388            lines.append(f"{indent}  - {expression.sql()}")
+389
+390        return lines
 
@@ -1810,46 +1870,46 @@ Projections:
-
373class SetOperation(Step):
-374    def __init__(
-375        self,
-376        op: t.Type[exp.Expression],
-377        left: str | None,
-378        right: str | None,
-379        distinct: bool = False,
-380    ) -> None:
-381        super().__init__()
-382        self.op = op
-383        self.left = left
-384        self.right = right
-385        self.distinct = distinct
-386
-387    @classmethod
-388    def from_expression(
-389        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
-390    ) -> Step:
-391        assert isinstance(expression, exp.Union)
-392        left = Step.from_expression(expression.left, ctes)
-393        right = Step.from_expression(expression.right, ctes)
-394        step = cls(
-395            op=expression.__class__,
-396            left=left.name,
-397            right=right.name,
-398            distinct=bool(expression.args.get("distinct")),
-399        )
-400        step.add_dependency(left)
-401        step.add_dependency(right)
-402        return step
-403
-404    def _to_s(self, indent: str) -> t.List[str]:
-405        lines = []
-406        if self.distinct:
-407            lines.append(f"{indent}Distinct: {self.distinct}")
-408        return lines
-409
-410    @property
-411    def type_name(self) -> str:
-412        return self.op.__name__
+            
393class SetOperation(Step):
+394    def __init__(
+395        self,
+396        op: t.Type[exp.Expression],
+397        left: str | None,
+398        right: str | None,
+399        distinct: bool = False,
+400    ) -> None:
+401        super().__init__()
+402        self.op = op
+403        self.left = left
+404        self.right = right
+405        self.distinct = distinct
+406
+407    @classmethod
+408    def from_expression(
+409        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
+410    ) -> Step:
+411        assert isinstance(expression, exp.Union)
+412        left = Step.from_expression(expression.left, ctes)
+413        right = Step.from_expression(expression.right, ctes)
+414        step = cls(
+415            op=expression.__class__,
+416            left=left.name,
+417            right=right.name,
+418            distinct=bool(expression.args.get("distinct")),
+419        )
+420        step.add_dependency(left)
+421        step.add_dependency(right)
+422        return step
+423
+424    def _to_s(self, indent: str) -> t.List[str]:
+425        lines = []
+426        if self.distinct:
+427            lines.append(f"{indent}Distinct: {self.distinct}")
+428        return lines
+429
+430    @property
+431    def type_name(self) -> str:
+432        return self.op.__name__
 
@@ -1865,18 +1925,18 @@ Projections:
-
374    def __init__(
-375        self,
-376        op: t.Type[exp.Expression],
-377        left: str | None,
-378        right: str | None,
-379        distinct: bool = False,
-380    ) -> None:
-381        super().__init__()
-382        self.op = op
-383        self.left = left
-384        self.right = right
-385        self.distinct = distinct
+            
394    def __init__(
+395        self,
+396        op: t.Type[exp.Expression],
+397        left: str | None,
+398        right: str | None,
+399        distinct: bool = False,
+400    ) -> None:
+401        super().__init__()
+402        self.op = op
+403        self.left = left
+404        self.right = right
+405        self.distinct = distinct
 
@@ -1939,22 +1999,22 @@ Projections:
-
387    @classmethod
-388    def from_expression(
-389        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
-390    ) -> Step:
-391        assert isinstance(expression, exp.Union)
-392        left = Step.from_expression(expression.left, ctes)
-393        right = Step.from_expression(expression.right, ctes)
-394        step = cls(
-395            op=expression.__class__,
-396            left=left.name,
-397            right=right.name,
-398            distinct=bool(expression.args.get("distinct")),
-399        )
-400        step.add_dependency(left)
-401        step.add_dependency(right)
-402        return step
+            
407    @classmethod
+408    def from_expression(
+409        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
+410    ) -> Step:
+411        assert isinstance(expression, exp.Union)
+412        left = Step.from_expression(expression.left, ctes)
+413        right = Step.from_expression(expression.right, ctes)
+414        step = cls(
+415            op=expression.__class__,
+416            left=left.name,
+417            right=right.name,
+418            distinct=bool(expression.args.get("distinct")),
+419        )
+420        step.add_dependency(left)
+421        step.add_dependency(right)
+422        return step
 
-- cgit v1.2.3