From 8d36f5966675e23bee7026ba37ae0647fbf47300 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 Apr 2024 10:11:53 +0200 Subject: Merging upstream version 23.7.0. Signed-off-by: Daniel Baumann --- docs/sqlglot/planner.html | 1819 +++++++++++++++++++++++---------------------- 1 file changed, 919 insertions(+), 900 deletions(-) (limited to 'docs/sqlglot/planner.html') diff --git a/docs/sqlglot/planner.html b/docs/sqlglot/planner.html index 830722f..c40b0ff 100644 --- a/docs/sqlglot/planner.html +++ b/docs/sqlglot/planner.html @@ -108,6 +108,9 @@
  • from_joins
  • +
  • + source_name +
  • joins
  • @@ -311,345 +314,347 @@ 118 if joins: 119 join = Join.from_joins(joins, ctes) 120 join.name = step.name -121 join.add_dependency(step) -122 step = join -123 -124 projections = [] # final selects in this chain of steps representing a select -125 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -126 aggregations = set() -127 next_operand_name = name_sequence("_a_") -128 -129 def extract_agg_operands(expression): -130 agg_funcs = tuple(expression.find_all(exp.AggFunc)) -131 if agg_funcs: -132 aggregations.add(expression) -133 -134 for agg in agg_funcs: -135 for operand in agg.unnest_operands(): -136 if isinstance(operand, exp.Column): -137 continue -138 if operand not in operands: -139 operands[operand] = next_operand_name() -140 -141 operand.replace(exp.column(operands[operand], quoted=True)) -142 -143 return bool(agg_funcs) -144 -145 def set_ops_and_aggs(step): -146 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) -147 step.aggregations = list(aggregations) -148 -149 for e in expression.expressions: -150 if e.find(exp.AggFunc): -151 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -152 extract_agg_operands(e) -153 else: -154 projections.append(e) -155 -156 where = expression.args.get("where") -157 -158 if where: -159 step.condition = where.this -160 -161 group = expression.args.get("group") -162 -163 if group or aggregations: -164 aggregate = Aggregate() -165 aggregate.source = step.name -166 aggregate.name = step.name -167 -168 having = expression.args.get("having") -169 -170 if having: -171 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): -172 aggregate.condition = exp.column("_h", step.name, quoted=True) -173 else: -174 aggregate.condition = having.this -175 -176 set_ops_and_aggs(aggregate) -177 -178 # give aggregates names and replace projections with references to them -179 aggregate.group = { -180 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -181 } -182 -183 intermediate: t.Dict[str | exp.Expression, str] = {} -184 for k, v in aggregate.group.items(): -185 intermediate[v] = k -186 if isinstance(v, exp.Column): -187 intermediate[v.name] = k -188 -189 for projection in projections: -190 for node, *_ in projection.walk(): -191 name = intermediate.get(node) -192 if name: -193 node.replace(exp.column(name, step.name)) -194 -195 if aggregate.condition: -196 for node, *_ in aggregate.condition.walk(): -197 name = intermediate.get(node) or intermediate.get(node.name) -198 if name: -199 node.replace(exp.column(name, step.name)) -200 -201 aggregate.add_dependency(step) -202 step = aggregate -203 -204 order = expression.args.get("order") -205 -206 if order: -207 if isinstance(step, Aggregate): -208 for i, ordered in enumerate(order.expressions): -209 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): -210 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) -211 -212 set_ops_and_aggs(aggregate) -213 -214 sort = Sort() -215 sort.name = step.name -216 sort.key = order.expressions -217 sort.add_dependency(step) -218 step = sort -219 -220 step.projections = projections -221 -222 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -223 distinct = Aggregate() -224 distinct.source = step.name -225 distinct.name = step.name -226 distinct.group = { -227 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -228 for e in projections or expression.expressions -229 } -230 distinct.add_dependency(step) -231 step = distinct -232 -233 limit = expression.args.get("limit") -234 -235 if limit: -236 step.limit = int(limit.text("expression")) -237 -238 return step -239 -240 def __init__(self) -> None: -241 self.name: t.Optional[str] = None -242 self.dependencies: t.Set[Step] = set() -243 self.dependents: t.Set[Step] = set() -244 self.projections: t.Sequence[exp.Expression] = [] -245 self.limit: float = math.inf -246 self.condition: t.Optional[exp.Expression] = None -247 -248 def add_dependency(self, dependency: Step) -> None: -249 self.dependencies.add(dependency) -250 dependency.dependents.add(self) -251 -252 def __repr__(self) -> str: -253 return self.to_s() -254 -255 def to_s(self, level: int = 0) -> str: -256 indent = " " * level -257 nested = f"{indent} " -258 -259 context = self._to_s(f"{nested} ") -260 -261 if context: -262 context = [f"{nested}Context:"] + context -263 -264 lines = [ -265 f"{indent}- {self.id}", -266 *context, -267 f"{nested}Projections:", -268 ] -269 -270 for expression in self.projections: -271 lines.append(f"{nested} - {expression.sql()}") -272 -273 if self.condition: -274 lines.append(f"{nested}Condition: {self.condition.sql()}") -275 -276 if self.limit is not math.inf: -277 lines.append(f"{nested}Limit: {self.limit}") -278 -279 if self.dependencies: -280 lines.append(f"{nested}Dependencies:") -281 for dependency in self.dependencies: -282 lines.append(" " + dependency.to_s(level + 1)) -283 -284 return "\n".join(lines) -285 -286 @property -287 def type_name(self) -> str: -288 return self.__class__.__name__ -289 -290 @property -291 def id(self) -> str: -292 name = self.name -293 name = f" {name}" if name else "" -294 return f"{self.type_name}:{name} ({id(self)})" -295 -296 def _to_s(self, _indent: str) -> t.List[str]: -297 return [] -298 +121 join.source_name = step.name +122 join.add_dependency(step) +123 step = join +124 +125 projections = [] # final selects in this chain of steps representing a select +126 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +127 aggregations = set() +128 next_operand_name = name_sequence("_a_") +129 +130 def extract_agg_operands(expression): +131 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +132 if agg_funcs: +133 aggregations.add(expression) +134 +135 for agg in agg_funcs: +136 for operand in agg.unnest_operands(): +137 if isinstance(operand, exp.Column): +138 continue +139 if operand not in operands: +140 operands[operand] = next_operand_name() +141 +142 operand.replace(exp.column(operands[operand], quoted=True)) +143 +144 return bool(agg_funcs) +145 +146 def set_ops_and_aggs(step): +147 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) +148 step.aggregations = list(aggregations) +149 +150 for e in expression.expressions: +151 if e.find(exp.AggFunc): +152 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +153 extract_agg_operands(e) +154 else: +155 projections.append(e) +156 +157 where = expression.args.get("where") +158 +159 if where: +160 step.condition = where.this +161 +162 group = expression.args.get("group") +163 +164 if group or aggregations: +165 aggregate = Aggregate() +166 aggregate.source = step.name +167 aggregate.name = step.name +168 +169 having = expression.args.get("having") +170 +171 if having: +172 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +173 aggregate.condition = exp.column("_h", step.name, quoted=True) +174 else: +175 aggregate.condition = having.this +176 +177 set_ops_and_aggs(aggregate) +178 +179 # give aggregates names and replace projections with references to them +180 aggregate.group = { +181 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +182 } +183 +184 intermediate: t.Dict[str | exp.Expression, str] = {} +185 for k, v in aggregate.group.items(): +186 intermediate[v] = k +187 if isinstance(v, exp.Column): +188 intermediate[v.name] = k +189 +190 for projection in projections: +191 for node in projection.walk(): +192 name = intermediate.get(node) +193 if name: +194 node.replace(exp.column(name, step.name)) +195 +196 if aggregate.condition: +197 for node in aggregate.condition.walk(): +198 name = intermediate.get(node) or intermediate.get(node.name) +199 if name: +200 node.replace(exp.column(name, step.name)) +201 +202 aggregate.add_dependency(step) +203 step = aggregate +204 +205 order = expression.args.get("order") +206 +207 if order: +208 if isinstance(step, Aggregate): +209 for i, ordered in enumerate(order.expressions): +210 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): +211 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) +212 +213 set_ops_and_aggs(aggregate) +214 +215 sort = Sort() +216 sort.name = step.name +217 sort.key = order.expressions +218 sort.add_dependency(step) +219 step = sort +220 +221 step.projections = projections +222 +223 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +224 distinct = Aggregate() +225 distinct.source = step.name +226 distinct.name = step.name +227 distinct.group = { +228 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +229 for e in projections or expression.expressions +230 } +231 distinct.add_dependency(step) +232 step = distinct +233 +234 limit = expression.args.get("limit") +235 +236 if limit: +237 step.limit = int(limit.text("expression")) +238 +239 return step +240 +241 def __init__(self) -> None: +242 self.name: t.Optional[str] = None +243 self.dependencies: t.Set[Step] = set() +244 self.dependents: t.Set[Step] = set() +245 self.projections: t.Sequence[exp.Expression] = [] +246 self.limit: float = math.inf +247 self.condition: t.Optional[exp.Expression] = None +248 +249 def add_dependency(self, dependency: Step) -> None: +250 self.dependencies.add(dependency) +251 dependency.dependents.add(self) +252 +253 def __repr__(self) -> str: +254 return self.to_s() +255 +256 def to_s(self, level: int = 0) -> str: +257 indent = " " * level +258 nested = f"{indent} " +259 +260 context = self._to_s(f"{nested} ") +261 +262 if context: +263 context = [f"{nested}Context:"] + context +264 +265 lines = [ +266 f"{indent}- {self.id}", +267 *context, +268 f"{nested}Projections:", +269 ] +270 +271 for expression in self.projections: +272 lines.append(f"{nested} - {expression.sql()}") +273 +274 if self.condition: +275 lines.append(f"{nested}Condition: {self.condition.sql()}") +276 +277 if self.limit is not math.inf: +278 lines.append(f"{nested}Limit: {self.limit}") +279 +280 if self.dependencies: +281 lines.append(f"{nested}Dependencies:") +282 for dependency in self.dependencies: +283 lines.append(" " + dependency.to_s(level + 1)) +284 +285 return "\n".join(lines) +286 +287 @property +288 def type_name(self) -> str: +289 return self.__class__.__name__ +290 +291 @property +292 def id(self) -> str: +293 name = self.name +294 name = f" {name}" if name else "" +295 return f"{self.type_name}:{name} ({id(self)})" +296 +297 def _to_s(self, _indent: str) -> t.List[str]: +298 return [] 299 -300class Scan(Step): -301 @classmethod -302 def from_expression( -303 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None -304 ) -> Step: -305 table = expression -306 alias_ = expression.alias_or_name -307 -308 if isinstance(expression, exp.Subquery): -309 table = expression.this -310 step = Step.from_expression(table, ctes) -311 step.name = alias_ -312 return step -313 -314 step = Scan() -315 step.name = alias_ -316 step.source = expression -317 if ctes and table.name in ctes: -318 step.add_dependency(ctes[table.name]) -319 -320 return step -321 -322 def __init__(self) -> None: -323 super().__init__() -324 self.source: t.Optional[exp.Expression] = None -325 -326 def _to_s(self, indent: str) -> t.List[str]: -327 return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore -328 +300 +301class Scan(Step): +302 @classmethod +303 def from_expression( +304 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None +305 ) -> Step: +306 table = expression +307 alias_ = expression.alias_or_name +308 +309 if isinstance(expression, exp.Subquery): +310 table = expression.this +311 step = Step.from_expression(table, ctes) +312 step.name = alias_ +313 return step +314 +315 step = Scan() +316 step.name = alias_ +317 step.source = expression +318 if ctes and table.name in ctes: +319 step.add_dependency(ctes[table.name]) +320 +321 return step +322 +323 def __init__(self) -> None: +324 super().__init__() +325 self.source: t.Optional[exp.Expression] = None +326 +327 def _to_s(self, indent: str) -> t.List[str]: +328 return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore 329 -330class Join(Step): -331 @classmethod -332 def from_joins( -333 cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None -334 ) -> Step: -335 step = Join() -336 -337 for join in joins: -338 source_key, join_key, condition = join_condition(join) -339 step.joins[join.alias_or_name] = { -340 "side": join.side, # type: ignore -341 "join_key": join_key, -342 "source_key": source_key, -343 "condition": condition, -344 } -345 -346 step.add_dependency(Scan.from_expression(join.this, ctes)) -347 -348 return step -349 -350 def __init__(self) -> None: -351 super().__init__() -352 self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {} -353 -354 def _to_s(self, indent: str) -> t.List[str]: -355 lines = [] -356 for name, join in self.joins.items(): -357 lines.append(f"{indent}{name}: {join['side'] or 'INNER'}") -358 join_key = ", ".join(str(key) for key in t.cast(list, join.get("join_key") or [])) -359 if join_key: -360 lines.append(f"{indent}Key: {join_key}") -361 if join.get("condition"): -362 lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore -363 return lines -364 -365 -366class Aggregate(Step): -367 def __init__(self) -> None: -368 super().__init__() -369 self.aggregations: t.List[exp.Expression] = [] -370 self.operands: t.Tuple[exp.Expression, ...] = () -371 self.group: t.Dict[str, exp.Expression] = {} -372 self.source: t.Optional[str] = None -373 -374 def _to_s(self, indent: str) -> t.List[str]: -375 lines = [f"{indent}Aggregations:"] -376 -377 for expression in self.aggregations: -378 lines.append(f"{indent} - {expression.sql()}") -379 -380 if self.group: -381 lines.append(f"{indent}Group:") -382 for expression in self.group.values(): -383 lines.append(f"{indent} - {expression.sql()}") -384 if self.condition: -385 lines.append(f"{indent}Having:") -386 lines.append(f"{indent} - {self.condition.sql()}") -387 if self.operands: -388 lines.append(f"{indent}Operands:") -389 for expression in self.operands: -390 lines.append(f"{indent} - {expression.sql()}") -391 -392 return lines +330 +331class Join(Step): +332 @classmethod +333 def from_joins( +334 cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None +335 ) -> Join: +336 step = Join() +337 +338 for join in joins: +339 source_key, join_key, condition = join_condition(join) +340 step.joins[join.alias_or_name] = { +341 "side": join.side, # type: ignore +342 "join_key": join_key, +343 "source_key": source_key, +344 "condition": condition, +345 } +346 +347 step.add_dependency(Scan.from_expression(join.this, ctes)) +348 +349 return step +350 +351 def __init__(self) -> None: +352 super().__init__() +353 self.source_name: t.Optional[str] = None +354 self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {} +355 +356 def _to_s(self, indent: str) -> t.List[str]: +357 lines = [f"{indent}Source: {self.source_name or self.name}"] +358 for name, join in self.joins.items(): +359 lines.append(f"{indent}{name}: {join['side'] or 'INNER'}") +360 join_key = ", ".join(str(key) for key in t.cast(list, join.get("join_key") or [])) +361 if join_key: +362 lines.append(f"{indent}Key: {join_key}") +363 if join.get("condition"): +364 lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore +365 return lines +366 +367 +368class Aggregate(Step): +369 def __init__(self) -> None: +370 super().__init__() +371 self.aggregations: t.List[exp.Expression] = [] +372 self.operands: t.Tuple[exp.Expression, ...] = () +373 self.group: t.Dict[str, exp.Expression] = {} +374 self.source: t.Optional[str] = None +375 +376 def _to_s(self, indent: str) -> t.List[str]: +377 lines = [f"{indent}Aggregations:"] +378 +379 for expression in self.aggregations: +380 lines.append(f"{indent} - {expression.sql()}") +381 +382 if self.group: +383 lines.append(f"{indent}Group:") +384 for expression in self.group.values(): +385 lines.append(f"{indent} - {expression.sql()}") +386 if self.condition: +387 lines.append(f"{indent}Having:") +388 lines.append(f"{indent} - {self.condition.sql()}") +389 if self.operands: +390 lines.append(f"{indent}Operands:") +391 for expression in self.operands: +392 lines.append(f"{indent} - {expression.sql()}") 393 -394 -395class Sort(Step): -396 def __init__(self) -> None: -397 super().__init__() -398 self.key = None -399 -400 def _to_s(self, indent: str) -> t.List[str]: -401 lines = [f"{indent}Key:"] -402 -403 for expression in self.key: # type: ignore -404 lines.append(f"{indent} - {expression.sql()}") -405 -406 return lines +394 return lines +395 +396 +397class Sort(Step): +398 def __init__(self) -> None: +399 super().__init__() +400 self.key = None +401 +402 def _to_s(self, indent: str) -> t.List[str]: +403 lines = [f"{indent}Key:"] +404 +405 for expression in self.key: # type: ignore +406 lines.append(f"{indent} - {expression.sql()}") 407 -408 -409class SetOperation(Step): -410 def __init__( -411 self, -412 op: t.Type[exp.Expression], -413 left: str | None, -414 right: str | None, -415 distinct: bool = False, -416 ) -> None: -417 super().__init__() -418 self.op = op -419 self.left = left -420 self.right = right -421 self.distinct = distinct -422 -423 @classmethod -424 def from_expression( -425 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None -426 ) -> Step: -427 assert isinstance(expression, exp.Union) -428 -429 left = Step.from_expression(expression.left, ctes) -430 # SELECT 1 UNION SELECT 2 <-- these subqueries don't have names -431 left.name = left.name or "left" -432 right = Step.from_expression(expression.right, ctes) -433 right.name = right.name or "right" -434 step = cls( -435 op=expression.__class__, -436 left=left.name, -437 right=right.name, -438 distinct=bool(expression.args.get("distinct")), -439 ) -440 -441 step.add_dependency(left) -442 step.add_dependency(right) -443 -444 limit = expression.args.get("limit") +408 return lines +409 +410 +411class SetOperation(Step): +412 def __init__( +413 self, +414 op: t.Type[exp.Expression], +415 left: str | None, +416 right: str | None, +417 distinct: bool = False, +418 ) -> None: +419 super().__init__() +420 self.op = op +421 self.left = left +422 self.right = right +423 self.distinct = distinct +424 +425 @classmethod +426 def from_expression( +427 cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None +428 ) -> SetOperation: +429 assert isinstance(expression, exp.Union) +430 +431 left = Step.from_expression(expression.left, ctes) +432 # SELECT 1 UNION SELECT 2 <-- these subqueries don't have names +433 left.name = left.name or "left" +434 right = Step.from_expression(expression.right, ctes) +435 right.name = right.name or "right" +436 step = cls( +437 op=expression.__class__, +438 left=left.name, +439 right=right.name, +440 distinct=bool(expression.args.get("distinct")), +441 ) +442 +443 step.add_dependency(left) +444 step.add_dependency(right) 445 -446 if limit: -447 step.limit = int(limit.text("expression")) -448 -449 return step +446 limit = expression.args.get("limit") +447 +448 if limit: +449 step.limit = int(limit.text("expression")) 450 -451 def _to_s(self, indent: str) -> t.List[str]: -452 lines = [] -453 if self.distinct: -454 lines.append(f"{indent}Distinct: {self.distinct}") -455 return lines -456 -457 @property -458 def type_name(self) -> str: -459 return self.op.__name__ +451 return step +452 +453 def _to_s(self, indent: str) -> t.List[str]: +454 lines = [] +455 if self.distinct: +456 lines.append(f"{indent}Distinct: {self.distinct}") +457 return lines +458 +459 @property +460 def type_name(self) -> str: +461 return self.op.__name__ @@ -882,183 +887,184 @@ 119 if joins: 120 join = Join.from_joins(joins, ctes) 121 join.name = step.name -122 join.add_dependency(step) -123 step = join -124 -125 projections = [] # final selects in this chain of steps representing a select -126 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -127 aggregations = set() -128 next_operand_name = name_sequence("_a_") -129 -130 def extract_agg_operands(expression): -131 agg_funcs = tuple(expression.find_all(exp.AggFunc)) -132 if agg_funcs: -133 aggregations.add(expression) -134 -135 for agg in agg_funcs: -136 for operand in agg.unnest_operands(): -137 if isinstance(operand, exp.Column): -138 continue -139 if operand not in operands: -140 operands[operand] = next_operand_name() -141 -142 operand.replace(exp.column(operands[operand], quoted=True)) -143 -144 return bool(agg_funcs) -145 -146 def set_ops_and_aggs(step): -147 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) -148 step.aggregations = list(aggregations) -149 -150 for e in expression.expressions: -151 if e.find(exp.AggFunc): -152 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -153 extract_agg_operands(e) -154 else: -155 projections.append(e) -156 -157 where = expression.args.get("where") -158 -159 if where: -160 step.condition = where.this -161 -162 group = expression.args.get("group") -163 -164 if group or aggregations: -165 aggregate = Aggregate() -166 aggregate.source = step.name -167 aggregate.name = step.name -168 -169 having = expression.args.get("having") -170 -171 if having: -172 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): -173 aggregate.condition = exp.column("_h", step.name, quoted=True) -174 else: -175 aggregate.condition = having.this -176 -177 set_ops_and_aggs(aggregate) -178 -179 # give aggregates names and replace projections with references to them -180 aggregate.group = { -181 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -182 } -183 -184 intermediate: t.Dict[str | exp.Expression, str] = {} -185 for k, v in aggregate.group.items(): -186 intermediate[v] = k -187 if isinstance(v, exp.Column): -188 intermediate[v.name] = k -189 -190 for projection in projections: -191 for node, *_ in projection.walk(): -192 name = intermediate.get(node) -193 if name: -194 node.replace(exp.column(name, step.name)) -195 -196 if aggregate.condition: -197 for node, *_ in aggregate.condition.walk(): -198 name = intermediate.get(node) or intermediate.get(node.name) -199 if name: -200 node.replace(exp.column(name, step.name)) -201 -202 aggregate.add_dependency(step) -203 step = aggregate -204 -205 order = expression.args.get("order") -206 -207 if order: -208 if isinstance(step, Aggregate): -209 for i, ordered in enumerate(order.expressions): -210 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): -211 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) -212 -213 set_ops_and_aggs(aggregate) -214 -215 sort = Sort() -216 sort.name = step.name -217 sort.key = order.expressions -218 sort.add_dependency(step) -219 step = sort -220 -221 step.projections = projections -222 -223 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -224 distinct = Aggregate() -225 distinct.source = step.name -226 distinct.name = step.name -227 distinct.group = { -228 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -229 for e in projections or expression.expressions -230 } -231 distinct.add_dependency(step) -232 step = distinct -233 -234 limit = expression.args.get("limit") -235 -236 if limit: -237 step.limit = int(limit.text("expression")) -238 -239 return step -240 -241 def __init__(self) -> None: -242 self.name: t.Optional[str] = None -243 self.dependencies: t.Set[Step] = set() -244 self.dependents: t.Set[Step] = set() -245 self.projections: t.Sequence[exp.Expression] = [] -246 self.limit: float = math.inf -247 self.condition: t.Optional[exp.Expression] = None -248 -249 def add_dependency(self, dependency: Step) -> None: -250 self.dependencies.add(dependency) -251 dependency.dependents.add(self) -252 -253 def __repr__(self) -> str: -254 return self.to_s() -255 -256 def to_s(self, level: int = 0) -> str: -257 indent = " " * level -258 nested = f"{indent} " -259 -260 context = self._to_s(f"{nested} ") -261 -262 if context: -263 context = [f"{nested}Context:"] + context -264 -265 lines = [ -266 f"{indent}- {self.id}", -267 *context, -268 f"{nested}Projections:", -269 ] -270 -271 for expression in self.projections: -272 lines.append(f"{nested} - {expression.sql()}") -273 -274 if self.condition: -275 lines.append(f"{nested}Condition: {self.condition.sql()}") -276 -277 if self.limit is not math.inf: -278 lines.append(f"{nested}Limit: {self.limit}") -279 -280 if self.dependencies: -281 lines.append(f"{nested}Dependencies:") -282 for dependency in self.dependencies: -283 lines.append(" " + dependency.to_s(level + 1)) -284 -285 return "\n".join(lines) -286 -287 @property -288 def type_name(self) -> str: -289 return self.__class__.__name__ -290 -291 @property -292 def id(self) -> str: -293 name = self.name -294 name = f" {name}" if name else "" -295 return f"{self.type_name}:{name} ({id(self)})" -296 -297 def _to_s(self, _indent: str) -> t.List[str]: -298 return [] +122 join.source_name = step.name +123 join.add_dependency(step) +124 step = join +125 +126 projections = [] # final selects in this chain of steps representing a select +127 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +128 aggregations = set() +129 next_operand_name = name_sequence("_a_") +130 +131 def extract_agg_operands(expression): +132 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +133 if agg_funcs: +134 aggregations.add(expression) +135 +136 for agg in agg_funcs: +137 for operand in agg.unnest_operands(): +138 if isinstance(operand, exp.Column): +139 continue +140 if operand not in operands: +141 operands[operand] = next_operand_name() +142 +143 operand.replace(exp.column(operands[operand], quoted=True)) +144 +145 return bool(agg_funcs) +146 +147 def set_ops_and_aggs(step): +148 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) +149 step.aggregations = list(aggregations) +150 +151 for e in expression.expressions: +152 if e.find(exp.AggFunc): +153 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +154 extract_agg_operands(e) +155 else: +156 projections.append(e) +157 +158 where = expression.args.get("where") +159 +160 if where: +161 step.condition = where.this +162 +163 group = expression.args.get("group") +164 +165 if group or aggregations: +166 aggregate = Aggregate() +167 aggregate.source = step.name +168 aggregate.name = step.name +169 +170 having = expression.args.get("having") +171 +172 if having: +173 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +174 aggregate.condition = exp.column("_h", step.name, quoted=True) +175 else: +176 aggregate.condition = having.this +177 +178 set_ops_and_aggs(aggregate) +179 +180 # give aggregates names and replace projections with references to them +181 aggregate.group = { +182 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +183 } +184 +185 intermediate: t.Dict[str | exp.Expression, str] = {} +186 for k, v in aggregate.group.items(): +187 intermediate[v] = k +188 if isinstance(v, exp.Column): +189 intermediate[v.name] = k +190 +191 for projection in projections: +192 for node in projection.walk(): +193 name = intermediate.get(node) +194 if name: +195 node.replace(exp.column(name, step.name)) +196 +197 if aggregate.condition: +198 for node in aggregate.condition.walk(): +199 name = intermediate.get(node) or intermediate.get(node.name) +200 if name: +201 node.replace(exp.column(name, step.name)) +202 +203 aggregate.add_dependency(step) +204 step = aggregate +205 +206 order = expression.args.get("order") +207 +208 if order: +209 if isinstance(step, Aggregate): +210 for i, ordered in enumerate(order.expressions): +211 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): +212 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) +213 +214 set_ops_and_aggs(aggregate) +215 +216 sort = Sort() +217 sort.name = step.name +218 sort.key = order.expressions +219 sort.add_dependency(step) +220 step = sort +221 +222 step.projections = projections +223 +224 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +225 distinct = Aggregate() +226 distinct.source = step.name +227 distinct.name = step.name +228 distinct.group = { +229 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +230 for e in projections or expression.expressions +231 } +232 distinct.add_dependency(step) +233 step = distinct +234 +235 limit = expression.args.get("limit") +236 +237 if limit: +238 step.limit = int(limit.text("expression")) +239 +240 return step +241 +242 def __init__(self) -> None: +243 self.name: t.Optional[str] = None +244 self.dependencies: t.Set[Step] = set() +245 self.dependents: t.Set[Step] = set() +246 self.projections: t.Sequence[exp.Expression] = [] +247 self.limit: float = math.inf +248 self.condition: t.Optional[exp.Expression] = None +249 +250 def add_dependency(self, dependency: Step) -> None: +251 self.dependencies.add(dependency) +252 dependency.dependents.add(self) +253 +254 def __repr__(self) -> str: +255 return self.to_s() +256 +257 def to_s(self, level: int = 0) -> str: +258 indent = " " * level +259 nested = f"{indent} " +260 +261 context = self._to_s(f"{nested} ") +262 +263 if context: +264 context = [f"{nested}Context:"] + context +265 +266 lines = [ +267 f"{indent}- {self.id}", +268 *context, +269 f"{nested}Projections:", +270 ] +271 +272 for expression in self.projections: +273 lines.append(f"{nested} - {expression.sql()}") +274 +275 if self.condition: +276 lines.append(f"{nested}Condition: {self.condition.sql()}") +277 +278 if self.limit is not math.inf: +279 lines.append(f"{nested}Limit: {self.limit}") +280 +281 if self.dependencies: +282 lines.append(f"{nested}Dependencies:") +283 for dependency in self.dependencies: +284 lines.append(" " + dependency.to_s(level + 1)) +285 +286 return "\n".join(lines) +287 +288 @property +289 def type_name(self) -> str: +290 return self.__class__.__name__ +291 +292 @property +293 def id(self) -> str: +294 name = self.name +295 name = f" {name}" if name else "" +296 return f"{self.type_name}:{name} ({id(self)})" +297 +298 def _to_s(self, _indent: str) -> t.List[str]: +299 return [] @@ -1153,124 +1159,125 @@ 119 if joins: 120 join = Join.from_joins(joins, ctes) 121 join.name = step.name -122 join.add_dependency(step) -123 step = join -124 -125 projections = [] # final selects in this chain of steps representing a select -126 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) -127 aggregations = set() -128 next_operand_name = name_sequence("_a_") -129 -130 def extract_agg_operands(expression): -131 agg_funcs = tuple(expression.find_all(exp.AggFunc)) -132 if agg_funcs: -133 aggregations.add(expression) -134 -135 for agg in agg_funcs: -136 for operand in agg.unnest_operands(): -137 if isinstance(operand, exp.Column): -138 continue -139 if operand not in operands: -140 operands[operand] = next_operand_name() -141 -142 operand.replace(exp.column(operands[operand], quoted=True)) -143 -144 return bool(agg_funcs) -145 -146 def set_ops_and_aggs(step): -147 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) -148 step.aggregations = list(aggregations) -149 -150 for e in expression.expressions: -151 if e.find(exp.AggFunc): -152 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) -153 extract_agg_operands(e) -154 else: -155 projections.append(e) -156 -157 where = expression.args.get("where") -158 -159 if where: -160 step.condition = where.this -161 -162 group = expression.args.get("group") -163 -164 if group or aggregations: -165 aggregate = Aggregate() -166 aggregate.source = step.name -167 aggregate.name = step.name -168 -169 having = expression.args.get("having") -170 -171 if having: -172 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): -173 aggregate.condition = exp.column("_h", step.name, quoted=True) -174 else: -175 aggregate.condition = having.this -176 -177 set_ops_and_aggs(aggregate) -178 -179 # give aggregates names and replace projections with references to them -180 aggregate.group = { -181 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) -182 } -183 -184 intermediate: t.Dict[str | exp.Expression, str] = {} -185 for k, v in aggregate.group.items(): -186 intermediate[v] = k -187 if isinstance(v, exp.Column): -188 intermediate[v.name] = k -189 -190 for projection in projections: -191 for node, *_ in projection.walk(): -192 name = intermediate.get(node) -193 if name: -194 node.replace(exp.column(name, step.name)) -195 -196 if aggregate.condition: -197 for node, *_ in aggregate.condition.walk(): -198 name = intermediate.get(node) or intermediate.get(node.name) -199 if name: -200 node.replace(exp.column(name, step.name)) -201 -202 aggregate.add_dependency(step) -203 step = aggregate -204 -205 order = expression.args.get("order") -206 -207 if order: -208 if isinstance(step, Aggregate): -209 for i, ordered in enumerate(order.expressions): -210 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): -211 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) -212 -213 set_ops_and_aggs(aggregate) -214 -215 sort = Sort() -216 sort.name = step.name -217 sort.key = order.expressions -218 sort.add_dependency(step) -219 step = sort -220 -221 step.projections = projections -222 -223 if isinstance(expression, exp.Select) and expression.args.get("distinct"): -224 distinct = Aggregate() -225 distinct.source = step.name -226 distinct.name = step.name -227 distinct.group = { -228 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) -229 for e in projections or expression.expressions -230 } -231 distinct.add_dependency(step) -232 step = distinct -233 -234 limit = expression.args.get("limit") -235 -236 if limit: -237 step.limit = int(limit.text("expression")) -238 -239 return step +122 join.source_name = step.name +123 join.add_dependency(step) +124 step = join +125 +126 projections = [] # final selects in this chain of steps representing a select +127 operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) +128 aggregations = set() +129 next_operand_name = name_sequence("_a_") +130 +131 def extract_agg_operands(expression): +132 agg_funcs = tuple(expression.find_all(exp.AggFunc)) +133 if agg_funcs: +134 aggregations.add(expression) +135 +136 for agg in agg_funcs: +137 for operand in agg.unnest_operands(): +138 if isinstance(operand, exp.Column): +139 continue +140 if operand not in operands: +141 operands[operand] = next_operand_name() +142 +143 operand.replace(exp.column(operands[operand], quoted=True)) +144 +145 return bool(agg_funcs) +146 +147 def set_ops_and_aggs(step): +148 step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items()) +149 step.aggregations = list(aggregations) +150 +151 for e in expression.expressions: +152 if e.find(exp.AggFunc): +153 projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) +154 extract_agg_operands(e) +155 else: +156 projections.append(e) +157 +158 where = expression.args.get("where") +159 +160 if where: +161 step.condition = where.this +162 +163 group = expression.args.get("group") +164 +165 if group or aggregations: +166 aggregate = Aggregate() +167 aggregate.source = step.name +168 aggregate.name = step.name +169 +170 having = expression.args.get("having") +171 +172 if having: +173 if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): +174 aggregate.condition = exp.column("_h", step.name, quoted=True) +175 else: +176 aggregate.condition = having.this +177 +178 set_ops_and_aggs(aggregate) +179 +180 # give aggregates names and replace projections with references to them +181 aggregate.group = { +182 f"_g{i}": e for i, e in enumerate(group.expressions if group else []) +183 } +184 +185 intermediate: t.Dict[str | exp.Expression, str] = {} +186 for k, v in aggregate.group.items(): +187 intermediate[v] = k +188 if isinstance(v, exp.Column): +189 intermediate[v.name] = k +190 +191 for projection in projections: +192 for node in projection.walk(): +193 name = intermediate.get(node) +194 if name: +195 node.replace(exp.column(name, step.name)) +196 +197 if aggregate.condition: +198 for node in aggregate.condition.walk(): +199 name = intermediate.get(node) or intermediate.get(node.name) +200 if name: +201 node.replace(exp.column(name, step.name)) +202 +203 aggregate.add_dependency(step) +204 step = aggregate +205 +206 order = expression.args.get("order") +207 +208 if order: +209 if isinstance(step, Aggregate): +210 for i, ordered in enumerate(order.expressions): +211 if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)): +212 ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True)) +213 +214 set_ops_and_aggs(aggregate) +215 +216 sort = Sort() +217 sort.name = step.name +218 sort.key = order.expressions +219 sort.add_dependency(step) +220 step = sort +221 +222 step.projections = projections +223 +224 if isinstance(expression, exp.Select) and expression.args.get("distinct"): +225 distinct = Aggregate() +226 distinct.source = step.name +227 distinct.name = step.name +228 distinct.group = { +229 e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) +230 for e in projections or expression.expressions +231 } +232 distinct.add_dependency(step) +233 step = distinct +234 +235 limit = expression.args.get("limit") +236 +237 if limit: +238 step.limit = int(limit.text("expression")) +239 +240 return step @@ -1412,9 +1419,9 @@ Projections: -
    249    def add_dependency(self, dependency: Step) -> None:
    -250        self.dependencies.add(dependency)
    -251        dependency.dependents.add(self)
    +            
    250    def add_dependency(self, dependency: Step) -> None:
    +251        self.dependencies.add(dependency)
    +252        dependency.dependents.add(self)
     
    @@ -1432,36 +1439,36 @@ Projections:
    -
    256    def to_s(self, level: int = 0) -> str:
    -257        indent = "  " * level
    -258        nested = f"{indent}    "
    -259
    -260        context = self._to_s(f"{nested}  ")
    -261
    -262        if context:
    -263            context = [f"{nested}Context:"] + context
    -264
    -265        lines = [
    -266            f"{indent}- {self.id}",
    -267            *context,
    -268            f"{nested}Projections:",
    -269        ]
    -270
    -271        for expression in self.projections:
    -272            lines.append(f"{nested}  - {expression.sql()}")
    -273
    -274        if self.condition:
    -275            lines.append(f"{nested}Condition: {self.condition.sql()}")
    -276
    -277        if self.limit is not math.inf:
    -278            lines.append(f"{nested}Limit: {self.limit}")
    -279
    -280        if self.dependencies:
    -281            lines.append(f"{nested}Dependencies:")
    -282            for dependency in self.dependencies:
    -283                lines.append("  " + dependency.to_s(level + 1))
    -284
    -285        return "\n".join(lines)
    +            
    257    def to_s(self, level: int = 0) -> str:
    +258        indent = "  " * level
    +259        nested = f"{indent}    "
    +260
    +261        context = self._to_s(f"{nested}  ")
    +262
    +263        if context:
    +264            context = [f"{nested}Context:"] + context
    +265
    +266        lines = [
    +267            f"{indent}- {self.id}",
    +268            *context,
    +269            f"{nested}Projections:",
    +270        ]
    +271
    +272        for expression in self.projections:
    +273            lines.append(f"{nested}  - {expression.sql()}")
    +274
    +275        if self.condition:
    +276            lines.append(f"{nested}Condition: {self.condition.sql()}")
    +277
    +278        if self.limit is not math.inf:
    +279            lines.append(f"{nested}Limit: {self.limit}")
    +280
    +281        if self.dependencies:
    +282            lines.append(f"{nested}Dependencies:")
    +283            for dependency in self.dependencies:
    +284                lines.append("  " + dependency.to_s(level + 1))
    +285
    +286        return "\n".join(lines)
     
    @@ -1477,9 +1484,9 @@ Projections:
    -
    287    @property
    -288    def type_name(self) -> str:
    -289        return self.__class__.__name__
    +            
    288    @property
    +289    def type_name(self) -> str:
    +290        return self.__class__.__name__
     
    @@ -1495,11 +1502,11 @@ Projections:
    -
    291    @property
    -292    def id(self) -> str:
    -293        name = self.name
    -294        name = f" {name}" if name else ""
    -295        return f"{self.type_name}:{name} ({id(self)})"
    +            
    292    @property
    +293    def id(self) -> str:
    +294        name = self.name
    +295        name = f" {name}" if name else ""
    +296        return f"{self.type_name}:{name} ({id(self)})"
     
    @@ -1518,34 +1525,34 @@ Projections:
    -
    301class Scan(Step):
    -302    @classmethod
    -303    def from_expression(
    -304        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    -305    ) -> Step:
    -306        table = expression
    -307        alias_ = expression.alias_or_name
    -308
    -309        if isinstance(expression, exp.Subquery):
    -310            table = expression.this
    -311            step = Step.from_expression(table, ctes)
    -312            step.name = alias_
    -313            return step
    -314
    -315        step = Scan()
    -316        step.name = alias_
    -317        step.source = expression
    -318        if ctes and table.name in ctes:
    -319            step.add_dependency(ctes[table.name])
    -320
    -321        return step
    -322
    -323    def __init__(self) -> None:
    -324        super().__init__()
    -325        self.source: t.Optional[exp.Expression] = None
    -326
    -327    def _to_s(self, indent: str) -> t.List[str]:
    -328        return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"]  # type: ignore
    +            
    302class Scan(Step):
    +303    @classmethod
    +304    def from_expression(
    +305        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    +306    ) -> Step:
    +307        table = expression
    +308        alias_ = expression.alias_or_name
    +309
    +310        if isinstance(expression, exp.Subquery):
    +311            table = expression.this
    +312            step = Step.from_expression(table, ctes)
    +313            step.name = alias_
    +314            return step
    +315
    +316        step = Scan()
    +317        step.name = alias_
    +318        step.source = expression
    +319        if ctes and table.name in ctes:
    +320            step.add_dependency(ctes[table.name])
    +321
    +322        return step
    +323
    +324    def __init__(self) -> None:
    +325        super().__init__()
    +326        self.source: t.Optional[exp.Expression] = None
    +327
    +328    def _to_s(self, indent: str) -> t.List[str]:
    +329        return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"]  # type: ignore
     
    @@ -1563,26 +1570,26 @@ Projections:
    -
    302    @classmethod
    -303    def from_expression(
    -304        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    -305    ) -> Step:
    -306        table = expression
    -307        alias_ = expression.alias_or_name
    -308
    -309        if isinstance(expression, exp.Subquery):
    -310            table = expression.this
    -311            step = Step.from_expression(table, ctes)
    -312            step.name = alias_
    -313            return step
    -314
    -315        step = Scan()
    -316        step.name = alias_
    -317        step.source = expression
    -318        if ctes and table.name in ctes:
    -319            step.add_dependency(ctes[table.name])
    -320
    -321        return step
    +            
    303    @classmethod
    +304    def from_expression(
    +305        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    +306    ) -> Step:
    +307        table = expression
    +308        alias_ = expression.alias_or_name
    +309
    +310        if isinstance(expression, exp.Subquery):
    +311            table = expression.this
    +312            step = Step.from_expression(table, ctes)
    +313            step.name = alias_
    +314            return step
    +315
    +316        step = Scan()
    +317        step.name = alias_
    +318        step.source = expression
    +319        if ctes and table.name in ctes:
    +320            step.add_dependency(ctes[table.name])
    +321
    +322        return step
     
    @@ -1688,40 +1695,41 @@ Projections:
    -
    331class Join(Step):
    -332    @classmethod
    -333    def from_joins(
    -334        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
    -335    ) -> Step:
    -336        step = Join()
    -337
    -338        for join in joins:
    -339            source_key, join_key, condition = join_condition(join)
    -340            step.joins[join.alias_or_name] = {
    -341                "side": join.side,  # type: ignore
    -342                "join_key": join_key,
    -343                "source_key": source_key,
    -344                "condition": condition,
    -345            }
    -346
    -347            step.add_dependency(Scan.from_expression(join.this, ctes))
    -348
    -349        return step
    -350
    -351    def __init__(self) -> None:
    -352        super().__init__()
    -353        self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {}
    -354
    -355    def _to_s(self, indent: str) -> t.List[str]:
    -356        lines = []
    -357        for name, join in self.joins.items():
    -358            lines.append(f"{indent}{name}: {join['side'] or 'INNER'}")
    -359            join_key = ", ".join(str(key) for key in t.cast(list, join.get("join_key") or []))
    -360            if join_key:
    -361                lines.append(f"{indent}Key: {join_key}")
    -362            if join.get("condition"):
    -363                lines.append(f"{indent}On: {join['condition'].sql()}")  # type: ignore
    -364        return lines
    +            
    332class Join(Step):
    +333    @classmethod
    +334    def from_joins(
    +335        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
    +336    ) -> Join:
    +337        step = Join()
    +338
    +339        for join in joins:
    +340            source_key, join_key, condition = join_condition(join)
    +341            step.joins[join.alias_or_name] = {
    +342                "side": join.side,  # type: ignore
    +343                "join_key": join_key,
    +344                "source_key": source_key,
    +345                "condition": condition,
    +346            }
    +347
    +348            step.add_dependency(Scan.from_expression(join.this, ctes))
    +349
    +350        return step
    +351
    +352    def __init__(self) -> None:
    +353        super().__init__()
    +354        self.source_name: t.Optional[str] = None
    +355        self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {}
    +356
    +357    def _to_s(self, indent: str) -> t.List[str]:
    +358        lines = [f"{indent}Source: {self.source_name or self.name}"]
    +359        for name, join in self.joins.items():
    +360            lines.append(f"{indent}{name}: {join['side'] or 'INNER'}")
    +361            join_key = ", ".join(str(key) for key in t.cast(list, join.get("join_key") or []))
    +362            if join_key:
    +363                lines.append(f"{indent}Key: {join_key}")
    +364            if join.get("condition"):
    +365                lines.append(f"{indent}On: {join['condition'].sql()}")  # type: ignore
    +366        return lines
     
    @@ -1733,35 +1741,46 @@ Projections:
    @classmethod
    def - from_joins( cls, joins: Iterable[sqlglot.expressions.Join], ctes: Optional[Dict[str, Step]] = None) -> Step: + from_joins( cls, joins: Iterable[sqlglot.expressions.Join], ctes: Optional[Dict[str, Step]] = None) -> Join:
    -
    332    @classmethod
    -333    def from_joins(
    -334        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
    -335    ) -> Step:
    -336        step = Join()
    -337
    -338        for join in joins:
    -339            source_key, join_key, condition = join_condition(join)
    -340            step.joins[join.alias_or_name] = {
    -341                "side": join.side,  # type: ignore
    -342                "join_key": join_key,
    -343                "source_key": source_key,
    -344                "condition": condition,
    -345            }
    -346
    -347            step.add_dependency(Scan.from_expression(join.this, ctes))
    -348
    -349        return step
    +            
    333    @classmethod
    +334    def from_joins(
    +335        cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
    +336    ) -> Join:
    +337        step = Join()
    +338
    +339        for join in joins:
    +340            source_key, join_key, condition = join_condition(join)
    +341            step.joins[join.alias_or_name] = {
    +342                "side": join.side,  # type: ignore
    +343                "join_key": join_key,
    +344                "source_key": source_key,
    +345                "condition": condition,
    +346            }
    +347
    +348            step.add_dependency(Scan.from_expression(join.this, ctes))
    +349
    +350        return step
     
    +
    +
    +
    + source_name: Optional[str] + + +
    + + + +
    @@ -1805,33 +1824,33 @@ Projections:
    -
    367class Aggregate(Step):
    -368    def __init__(self) -> None:
    -369        super().__init__()
    -370        self.aggregations: t.List[exp.Expression] = []
    -371        self.operands: t.Tuple[exp.Expression, ...] = ()
    -372        self.group: t.Dict[str, exp.Expression] = {}
    -373        self.source: t.Optional[str] = None
    -374
    -375    def _to_s(self, indent: str) -> t.List[str]:
    -376        lines = [f"{indent}Aggregations:"]
    -377
    -378        for expression in self.aggregations:
    -379            lines.append(f"{indent}  - {expression.sql()}")
    -380
    -381        if self.group:
    -382            lines.append(f"{indent}Group:")
    -383            for expression in self.group.values():
    -384                lines.append(f"{indent}  - {expression.sql()}")
    -385        if self.condition:
    -386            lines.append(f"{indent}Having:")
    -387            lines.append(f"{indent}  - {self.condition.sql()}")
    -388        if self.operands:
    -389            lines.append(f"{indent}Operands:")
    -390            for expression in self.operands:
    -391                lines.append(f"{indent}  - {expression.sql()}")
    -392
    -393        return lines
    +            
    369class Aggregate(Step):
    +370    def __init__(self) -> None:
    +371        super().__init__()
    +372        self.aggregations: t.List[exp.Expression] = []
    +373        self.operands: t.Tuple[exp.Expression, ...] = ()
    +374        self.group: t.Dict[str, exp.Expression] = {}
    +375        self.source: t.Optional[str] = None
    +376
    +377    def _to_s(self, indent: str) -> t.List[str]:
    +378        lines = [f"{indent}Aggregations:"]
    +379
    +380        for expression in self.aggregations:
    +381            lines.append(f"{indent}  - {expression.sql()}")
    +382
    +383        if self.group:
    +384            lines.append(f"{indent}Group:")
    +385            for expression in self.group.values():
    +386                lines.append(f"{indent}  - {expression.sql()}")
    +387        if self.condition:
    +388            lines.append(f"{indent}Having:")
    +389            lines.append(f"{indent}  - {self.condition.sql()}")
    +390        if self.operands:
    +391            lines.append(f"{indent}Operands:")
    +392            for expression in self.operands:
    +393                lines.append(f"{indent}  - {expression.sql()}")
    +394
    +395        return lines
     
    @@ -1912,18 +1931,18 @@ Projections:
    -
    396class Sort(Step):
    -397    def __init__(self) -> None:
    -398        super().__init__()
    -399        self.key = None
    -400
    -401    def _to_s(self, indent: str) -> t.List[str]:
    -402        lines = [f"{indent}Key:"]
    -403
    -404        for expression in self.key:  # type: ignore
    -405            lines.append(f"{indent}  - {expression.sql()}")
    -406
    -407        return lines
    +            
    398class Sort(Step):
    +399    def __init__(self) -> None:
    +400        super().__init__()
    +401        self.key = None
    +402
    +403    def _to_s(self, indent: str) -> t.List[str]:
    +404        lines = [f"{indent}Key:"]
    +405
    +406        for expression in self.key:  # type: ignore
    +407            lines.append(f"{indent}  - {expression.sql()}")
    +408
    +409        return lines
     
    @@ -1971,57 +1990,57 @@ Projections:
    -
    410class SetOperation(Step):
    -411    def __init__(
    -412        self,
    -413        op: t.Type[exp.Expression],
    -414        left: str | None,
    -415        right: str | None,
    -416        distinct: bool = False,
    -417    ) -> None:
    -418        super().__init__()
    -419        self.op = op
    -420        self.left = left
    -421        self.right = right
    -422        self.distinct = distinct
    -423
    -424    @classmethod
    -425    def from_expression(
    -426        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    -427    ) -> Step:
    -428        assert isinstance(expression, exp.Union)
    -429
    -430        left = Step.from_expression(expression.left, ctes)
    -431        # SELECT 1 UNION SELECT 2  <-- these subqueries don't have names
    -432        left.name = left.name or "left"
    -433        right = Step.from_expression(expression.right, ctes)
    -434        right.name = right.name or "right"
    -435        step = cls(
    -436            op=expression.__class__,
    -437            left=left.name,
    -438            right=right.name,
    -439            distinct=bool(expression.args.get("distinct")),
    -440        )
    -441
    -442        step.add_dependency(left)
    -443        step.add_dependency(right)
    -444
    -445        limit = expression.args.get("limit")
    +            
    412class SetOperation(Step):
    +413    def __init__(
    +414        self,
    +415        op: t.Type[exp.Expression],
    +416        left: str | None,
    +417        right: str | None,
    +418        distinct: bool = False,
    +419    ) -> None:
    +420        super().__init__()
    +421        self.op = op
    +422        self.left = left
    +423        self.right = right
    +424        self.distinct = distinct
    +425
    +426    @classmethod
    +427    def from_expression(
    +428        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    +429    ) -> SetOperation:
    +430        assert isinstance(expression, exp.Union)
    +431
    +432        left = Step.from_expression(expression.left, ctes)
    +433        # SELECT 1 UNION SELECT 2  <-- these subqueries don't have names
    +434        left.name = left.name or "left"
    +435        right = Step.from_expression(expression.right, ctes)
    +436        right.name = right.name or "right"
    +437        step = cls(
    +438            op=expression.__class__,
    +439            left=left.name,
    +440            right=right.name,
    +441            distinct=bool(expression.args.get("distinct")),
    +442        )
    +443
    +444        step.add_dependency(left)
    +445        step.add_dependency(right)
     446
    -447        if limit:
    -448            step.limit = int(limit.text("expression"))
    -449
    -450        return step
    +447        limit = expression.args.get("limit")
    +448
    +449        if limit:
    +450            step.limit = int(limit.text("expression"))
     451
    -452    def _to_s(self, indent: str) -> t.List[str]:
    -453        lines = []
    -454        if self.distinct:
    -455            lines.append(f"{indent}Distinct: {self.distinct}")
    -456        return lines
    -457
    -458    @property
    -459    def type_name(self) -> str:
    -460        return self.op.__name__
    +452        return step
    +453
    +454    def _to_s(self, indent: str) -> t.List[str]:
    +455        lines = []
    +456        if self.distinct:
    +457            lines.append(f"{indent}Distinct: {self.distinct}")
    +458        return lines
    +459
    +460    @property
    +461    def type_name(self) -> str:
    +462        return self.op.__name__
     
    @@ -2037,18 +2056,18 @@ Projections:
    -
    411    def __init__(
    -412        self,
    -413        op: t.Type[exp.Expression],
    -414        left: str | None,
    -415        right: str | None,
    -416        distinct: bool = False,
    -417    ) -> None:
    -418        super().__init__()
    -419        self.op = op
    -420        self.left = left
    -421        self.right = right
    -422        self.distinct = distinct
    +            
    413    def __init__(
    +414        self,
    +415        op: t.Type[exp.Expression],
    +416        left: str | None,
    +417        right: str | None,
    +418        distinct: bool = False,
    +419    ) -> None:
    +420        super().__init__()
    +421        self.op = op
    +422        self.left = left
    +423        self.right = right
    +424        self.distinct = distinct
     
    @@ -2105,39 +2124,39 @@ Projections:
    @classmethod
    def - from_expression( cls, expression: sqlglot.expressions.Expression, ctes: Optional[Dict[str, Step]] = None) -> Step: + from_expression( cls, expression: sqlglot.expressions.Expression, ctes: Optional[Dict[str, Step]] = None) -> SetOperation:
    -
    424    @classmethod
    -425    def from_expression(
    -426        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    -427    ) -> Step:
    -428        assert isinstance(expression, exp.Union)
    -429
    -430        left = Step.from_expression(expression.left, ctes)
    -431        # SELECT 1 UNION SELECT 2  <-- these subqueries don't have names
    -432        left.name = left.name or "left"
    -433        right = Step.from_expression(expression.right, ctes)
    -434        right.name = right.name or "right"
    -435        step = cls(
    -436            op=expression.__class__,
    -437            left=left.name,
    -438            right=right.name,
    -439            distinct=bool(expression.args.get("distinct")),
    -440        )
    -441
    -442        step.add_dependency(left)
    -443        step.add_dependency(right)
    -444
    -445        limit = expression.args.get("limit")
    +            
    426    @classmethod
    +427    def from_expression(
    +428        cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
    +429    ) -> SetOperation:
    +430        assert isinstance(expression, exp.Union)
    +431
    +432        left = Step.from_expression(expression.left, ctes)
    +433        # SELECT 1 UNION SELECT 2  <-- these subqueries don't have names
    +434        left.name = left.name or "left"
    +435        right = Step.from_expression(expression.right, ctes)
    +436        right.name = right.name or "right"
    +437        step = cls(
    +438            op=expression.__class__,
    +439            left=left.name,
    +440            right=right.name,
    +441            distinct=bool(expression.args.get("distinct")),
    +442        )
    +443
    +444        step.add_dependency(left)
    +445        step.add_dependency(right)
     446
    -447        if limit:
    -448            step.limit = int(limit.text("expression"))
    -449
    -450        return step
    +447        limit = expression.args.get("limit")
    +448
    +449        if limit:
    +450            step.limit = int(limit.text("expression"))
    +451
    +452        return step
     
    @@ -2211,9 +2230,9 @@ Projections:
    -
    458    @property
    -459    def type_name(self) -> str:
    -460        return self.op.__name__
    +            
    460    @property
    +461    def type_name(self) -> str:
    +462        return self.op.__name__
     
    -- cgit v1.2.3