From 20739a12c39121a9e7ad3c9a2469ec5a6876199d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 3 Jun 2023 01:59:40 +0200 Subject: Merging upstream version 15.0.0. Signed-off-by: Daniel Baumann --- docs/sqlglot/optimizer/pushdown_predicates.html | 662 ++++++++++++------------ 1 file changed, 333 insertions(+), 329 deletions(-) (limited to 'docs/sqlglot/optimizer/pushdown_predicates.html') diff --git a/docs/sqlglot/optimizer/pushdown_predicates.html b/docs/sqlglot/optimizer/pushdown_predicates.html index 4870176..67fac86 100644 --- a/docs/sqlglot/optimizer/pushdown_predicates.html +++ b/docs/sqlglot/optimizer/pushdown_predicates.html @@ -94,180 +94,182 @@ 21 sqlglot.Expression: optimized expression 22 """ 23 root = build_scope(expression) - 24 scope_ref_count = root.ref_count() - 25 - 26 for scope in reversed(list(root.traverse())): - 27 select = scope.expression - 28 where = select.args.get("where") - 29 if where: - 30 selected_sources = scope.selected_sources - 31 # a right join can only push down to itself and not the source FROM table - 32 for k, (node, source) in selected_sources.items(): - 33 parent = node.find_ancestor(exp.Join, exp.From) - 34 if isinstance(parent, exp.Join) and parent.side == "RIGHT": - 35 selected_sources = {k: (node, source)} - 36 break - 37 pushdown(where.this, selected_sources, scope_ref_count) - 38 - 39 # joins should only pushdown into itself, not to other joins - 40 # so we limit the selected sources to only itself - 41 for join in select.args.get("joins") or []: - 42 name = join.this.alias_or_name - 43 pushdown(join.args.get("on"), {name: scope.selected_sources[name]}, scope_ref_count) - 44 - 45 return expression + 24 + 25 if root: + 26 scope_ref_count = root.ref_count() + 27 + 28 for scope in reversed(list(root.traverse())): + 29 select = scope.expression + 30 where = select.args.get("where") + 31 if where: + 32 selected_sources = scope.selected_sources + 33 # a right join can only push down to itself and not the source FROM table + 34 for k, (node, source) in selected_sources.items(): + 35 parent = node.find_ancestor(exp.Join, exp.From) + 36 if isinstance(parent, exp.Join) and parent.side == "RIGHT": + 37 selected_sources = {k: (node, source)} + 38 break + 39 pushdown(where.this, selected_sources, scope_ref_count) + 40 + 41 # joins should only pushdown into itself, not to other joins + 42 # so we limit the selected sources to only itself + 43 for join in select.args.get("joins") or []: + 44 name = join.this.alias_or_name + 45 pushdown(join.args.get("on"), {name: scope.selected_sources[name]}, scope_ref_count) 46 - 47 - 48def pushdown(condition, sources, scope_ref_count): - 49 if not condition: - 50 return - 51 - 52 condition = condition.replace(simplify(condition)) - 53 cnf_like = normalized(condition) or not normalized(condition, dnf=True) - 54 - 55 predicates = list( - 56 condition.flatten() - 57 if isinstance(condition, exp.And if cnf_like else exp.Or) - 58 else [condition] - 59 ) - 60 - 61 if cnf_like: - 62 pushdown_cnf(predicates, sources, scope_ref_count) - 63 else: - 64 pushdown_dnf(predicates, sources, scope_ref_count) - 65 - 66 - 67def pushdown_cnf(predicates, scope, scope_ref_count): - 68 """ - 69 If the predicates are in CNF like form, we can simply replace each block in the parent. - 70 """ - 71 for predicate in predicates: - 72 for node in nodes_for_predicate(predicate, scope, scope_ref_count).values(): - 73 if isinstance(node, exp.Join): - 74 predicate.replace(exp.true()) - 75 node.on(predicate, copy=False) - 76 break - 77 if isinstance(node, exp.Select): - 78 predicate.replace(exp.true()) - 79 node.where(replace_aliases(node, predicate), copy=False) - 80 - 81 - 82def pushdown_dnf(predicates, scope, scope_ref_count): - 83 """ - 84 If the predicates are in DNF form, we can only push down conditions that are in all blocks. - 85 Additionally, we can't remove predicates from their original form. - 86 """ - 87 # find all the tables that can be pushdown too - 88 # these are tables that are referenced in all blocks of a DNF - 89 # (a.x AND b.x) OR (a.y AND c.y) - 90 # only table a can be push down - 91 pushdown_tables = set() - 92 - 93 for a in predicates: - 94 a_tables = set(exp.column_table_names(a)) - 95 - 96 for b in predicates: - 97 a_tables &= set(exp.column_table_names(b)) - 98 - 99 pushdown_tables.update(a_tables) + 47 return expression + 48 + 49 + 50def pushdown(condition, sources, scope_ref_count): + 51 if not condition: + 52 return + 53 + 54 condition = condition.replace(simplify(condition)) + 55 cnf_like = normalized(condition) or not normalized(condition, dnf=True) + 56 + 57 predicates = list( + 58 condition.flatten() + 59 if isinstance(condition, exp.And if cnf_like else exp.Or) + 60 else [condition] + 61 ) + 62 + 63 if cnf_like: + 64 pushdown_cnf(predicates, sources, scope_ref_count) + 65 else: + 66 pushdown_dnf(predicates, sources, scope_ref_count) + 67 + 68 + 69def pushdown_cnf(predicates, scope, scope_ref_count): + 70 """ + 71 If the predicates are in CNF like form, we can simply replace each block in the parent. + 72 """ + 73 for predicate in predicates: + 74 for node in nodes_for_predicate(predicate, scope, scope_ref_count).values(): + 75 if isinstance(node, exp.Join): + 76 predicate.replace(exp.true()) + 77 node.on(predicate, copy=False) + 78 break + 79 if isinstance(node, exp.Select): + 80 predicate.replace(exp.true()) + 81 node.where(replace_aliases(node, predicate), copy=False) + 82 + 83 + 84def pushdown_dnf(predicates, scope, scope_ref_count): + 85 """ + 86 If the predicates are in DNF form, we can only push down conditions that are in all blocks. + 87 Additionally, we can't remove predicates from their original form. + 88 """ + 89 # find all the tables that can be pushdown too + 90 # these are tables that are referenced in all blocks of a DNF + 91 # (a.x AND b.x) OR (a.y AND c.y) + 92 # only table a can be push down + 93 pushdown_tables = set() + 94 + 95 for a in predicates: + 96 a_tables = set(exp.column_table_names(a)) + 97 + 98 for b in predicates: + 99 a_tables &= set(exp.column_table_names(b)) 100 -101 conditions = {} +101 pushdown_tables.update(a_tables) 102 -103 # for every pushdown table, find all related conditions in all predicates -104 # combine them with ORS -105 # (a.x AND and a.y AND b.x) OR (a.z AND c.y) -> (a.x AND a.y) OR (a.z) -106 for table in sorted(pushdown_tables): -107 for predicate in predicates: -108 nodes = nodes_for_predicate(predicate, scope, scope_ref_count) -109 -110 if table not in nodes: -111 continue -112 -113 predicate_condition = None +103 conditions = {} +104 +105 # for every pushdown table, find all related conditions in all predicates +106 # combine them with ORS +107 # (a.x AND and a.y AND b.x) OR (a.z AND c.y) -> (a.x AND a.y) OR (a.z) +108 for table in sorted(pushdown_tables): +109 for predicate in predicates: +110 nodes = nodes_for_predicate(predicate, scope, scope_ref_count) +111 +112 if table not in nodes: +113 continue 114 -115 for column in predicate.find_all(exp.Column): -116 if column.table == table: -117 condition = column.find_ancestor(exp.Condition) -118 predicate_condition = ( -119 exp.and_(predicate_condition, condition) -120 if predicate_condition -121 else condition -122 ) -123 -124 if predicate_condition: -125 conditions[table] = ( -126 exp.or_(conditions[table], predicate_condition) -127 if table in conditions -128 else predicate_condition -129 ) -130 -131 for name, node in nodes.items(): -132 if name not in conditions: -133 continue -134 -135 predicate = conditions[name] +115 predicate_condition = None +116 +117 for column in predicate.find_all(exp.Column): +118 if column.table == table: +119 condition = column.find_ancestor(exp.Condition) +120 predicate_condition = ( +121 exp.and_(predicate_condition, condition) +122 if predicate_condition +123 else condition +124 ) +125 +126 if predicate_condition: +127 conditions[table] = ( +128 exp.or_(conditions[table], predicate_condition) +129 if table in conditions +130 else predicate_condition +131 ) +132 +133 for name, node in nodes.items(): +134 if name not in conditions: +135 continue 136 -137 if isinstance(node, exp.Join): -138 node.on(predicate, copy=False) -139 elif isinstance(node, exp.Select): -140 node.where(replace_aliases(node, predicate), copy=False) -141 -142 -143def nodes_for_predicate(predicate, sources, scope_ref_count): -144 nodes = {} -145 tables = exp.column_table_names(predicate) -146 where_condition = isinstance(predicate.find_ancestor(exp.Join, exp.Where), exp.Where) -147 -148 for table in tables: -149 node, source = sources.get(table) or (None, None) -150 -151 # if the predicate is in a where statement we can try to push it down -152 # we want to find the root join or from statement -153 if node and where_condition: -154 node = node.find_ancestor(exp.Join, exp.From) -155 -156 # a node can reference a CTE which should be pushed down -157 if isinstance(node, exp.From) and not isinstance(source, exp.Table): -158 with_ = source.parent.expression.args.get("with") -159 if with_ and with_.recursive: -160 return {} -161 node = source.expression -162 -163 if isinstance(node, exp.Join): -164 if node.side and node.side != "RIGHT": -165 return {} -166 nodes[table] = node -167 elif isinstance(node, exp.Select) and len(tables) == 1: -168 # We can't push down window expressions -169 has_window_expression = any( -170 select for select in node.selects if select.find(exp.Window) -171 ) -172 # we can't push down predicates to select statements if they are referenced in -173 # multiple places. -174 if ( -175 not node.args.get("group") -176 and scope_ref_count[id(source)] < 2 -177 and not has_window_expression -178 ): -179 nodes[table] = node -180 return nodes -181 -182 -183def replace_aliases(source, predicate): -184 aliases = {} -185 -186 for select in source.selects: -187 if isinstance(select, exp.Alias): -188 aliases[select.alias] = select.this -189 else: -190 aliases[select.name] = select -191 -192 def _replace_alias(column): -193 if isinstance(column, exp.Column) and column.name in aliases: -194 return aliases[column.name].copy() -195 return column -196 -197 return predicate.transform(_replace_alias) +137 predicate = conditions[name] +138 +139 if isinstance(node, exp.Join): +140 node.on(predicate, copy=False) +141 elif isinstance(node, exp.Select): +142 node.where(replace_aliases(node, predicate), copy=False) +143 +144 +145def nodes_for_predicate(predicate, sources, scope_ref_count): +146 nodes = {} +147 tables = exp.column_table_names(predicate) +148 where_condition = isinstance(predicate.find_ancestor(exp.Join, exp.Where), exp.Where) +149 +150 for table in tables: +151 node, source = sources.get(table) or (None, None) +152 +153 # if the predicate is in a where statement we can try to push it down +154 # we want to find the root join or from statement +155 if node and where_condition: +156 node = node.find_ancestor(exp.Join, exp.From) +157 +158 # a node can reference a CTE which should be pushed down +159 if isinstance(node, exp.From) and not isinstance(source, exp.Table): +160 with_ = source.parent.expression.args.get("with") +161 if with_ and with_.recursive: +162 return {} +163 node = source.expression +164 +165 if isinstance(node, exp.Join): +166 if node.side and node.side != "RIGHT": +167 return {} +168 nodes[table] = node +169 elif isinstance(node, exp.Select) and len(tables) == 1: +170 # We can't push down window expressions +171 has_window_expression = any( +172 select for select in node.selects if select.find(exp.Window) +173 ) +174 # we can't push down predicates to select statements if they are referenced in +175 # multiple places. +176 if ( +177 not node.args.get("group") +178 and scope_ref_count[id(source)] < 2 +179 and not has_window_expression +180 ): +181 nodes[table] = node +182 return nodes +183 +184 +185def replace_aliases(source, predicate): +186 aliases = {} +187 +188 for select in source.selects: +189 if isinstance(select, exp.Alias): +190 aliases[select.alias] = select.this +191 else: +192 aliases[select.name] = select +193 +194 def _replace_alias(column): +195 if isinstance(column, exp.Column) and column.name in aliases: +196 return aliases[column.name].copy() +197 return column +198 +199 return predicate.transform(_replace_alias) @@ -300,28 +302,30 @@ 22 sqlglot.Expression: optimized expression 23 """ 24 root = build_scope(expression) -25 scope_ref_count = root.ref_count() -26 -27 for scope in reversed(list(root.traverse())): -28 select = scope.expression -29 where = select.args.get("where") -30 if where: -31 selected_sources = scope.selected_sources -32 # a right join can only push down to itself and not the source FROM table -33 for k, (node, source) in selected_sources.items(): -34 parent = node.find_ancestor(exp.Join, exp.From) -35 if isinstance(parent, exp.Join) and parent.side == "RIGHT": -36 selected_sources = {k: (node, source)} -37 break -38 pushdown(where.this, selected_sources, scope_ref_count) -39 -40 # joins should only pushdown into itself, not to other joins -41 # so we limit the selected sources to only itself -42 for join in select.args.get("joins") or []: -43 name = join.this.alias_or_name -44 pushdown(join.args.get("on"), {name: scope.selected_sources[name]}, scope_ref_count) -45 -46 return expression +25 +26 if root: +27 scope_ref_count = root.ref_count() +28 +29 for scope in reversed(list(root.traverse())): +30 select = scope.expression +31 where = select.args.get("where") +32 if where: +33 selected_sources = scope.selected_sources +34 # a right join can only push down to itself and not the source FROM table +35 for k, (node, source) in selected_sources.items(): +36 parent = node.find_ancestor(exp.Join, exp.From) +37 if isinstance(parent, exp.Join) and parent.side == "RIGHT": +38 selected_sources = {k: (node, source)} +39 break +40 pushdown(where.this, selected_sources, scope_ref_count) +41 +42 # joins should only pushdown into itself, not to other joins +43 # so we limit the selected sources to only itself +44 for join in select.args.get("joins") or []: +45 name = join.this.alias_or_name +46 pushdown(join.args.get("on"), {name: scope.selected_sources[name]}, scope_ref_count) +47 +48 return expression @@ -366,23 +370,23 @@ -
49def pushdown(condition, sources, scope_ref_count):
-50    if not condition:
-51        return
-52
-53    condition = condition.replace(simplify(condition))
-54    cnf_like = normalized(condition) or not normalized(condition, dnf=True)
-55
-56    predicates = list(
-57        condition.flatten()
-58        if isinstance(condition, exp.And if cnf_like else exp.Or)
-59        else [condition]
-60    )
-61
-62    if cnf_like:
-63        pushdown_cnf(predicates, sources, scope_ref_count)
-64    else:
-65        pushdown_dnf(predicates, sources, scope_ref_count)
+            
51def pushdown(condition, sources, scope_ref_count):
+52    if not condition:
+53        return
+54
+55    condition = condition.replace(simplify(condition))
+56    cnf_like = normalized(condition) or not normalized(condition, dnf=True)
+57
+58    predicates = list(
+59        condition.flatten()
+60        if isinstance(condition, exp.And if cnf_like else exp.Or)
+61        else [condition]
+62    )
+63
+64    if cnf_like:
+65        pushdown_cnf(predicates, sources, scope_ref_count)
+66    else:
+67        pushdown_dnf(predicates, sources, scope_ref_count)
 
@@ -400,19 +404,19 @@
-
68def pushdown_cnf(predicates, scope, scope_ref_count):
-69    """
-70    If the predicates are in CNF like form, we can simply replace each block in the parent.
-71    """
-72    for predicate in predicates:
-73        for node in nodes_for_predicate(predicate, scope, scope_ref_count).values():
-74            if isinstance(node, exp.Join):
-75                predicate.replace(exp.true())
-76                node.on(predicate, copy=False)
-77                break
-78            if isinstance(node, exp.Select):
-79                predicate.replace(exp.true())
-80                node.where(replace_aliases(node, predicate), copy=False)
+            
70def pushdown_cnf(predicates, scope, scope_ref_count):
+71    """
+72    If the predicates are in CNF like form, we can simply replace each block in the parent.
+73    """
+74    for predicate in predicates:
+75        for node in nodes_for_predicate(predicate, scope, scope_ref_count).values():
+76            if isinstance(node, exp.Join):
+77                predicate.replace(exp.true())
+78                node.on(predicate, copy=False)
+79                break
+80            if isinstance(node, exp.Select):
+81                predicate.replace(exp.true())
+82                node.where(replace_aliases(node, predicate), copy=False)
 
@@ -432,65 +436,65 @@
-
 83def pushdown_dnf(predicates, scope, scope_ref_count):
- 84    """
- 85    If the predicates are in DNF form, we can only push down conditions that are in all blocks.
- 86    Additionally, we can't remove predicates from their original form.
- 87    """
- 88    # find all the tables that can be pushdown too
- 89    # these are tables that are referenced in all blocks of a DNF
- 90    # (a.x AND b.x) OR (a.y AND c.y)
- 91    # only table a can be push down
- 92    pushdown_tables = set()
- 93
- 94    for a in predicates:
- 95        a_tables = set(exp.column_table_names(a))
- 96
- 97        for b in predicates:
- 98            a_tables &= set(exp.column_table_names(b))
- 99
-100        pushdown_tables.update(a_tables)
+            
 85def pushdown_dnf(predicates, scope, scope_ref_count):
+ 86    """
+ 87    If the predicates are in DNF form, we can only push down conditions that are in all blocks.
+ 88    Additionally, we can't remove predicates from their original form.
+ 89    """
+ 90    # find all the tables that can be pushdown too
+ 91    # these are tables that are referenced in all blocks of a DNF
+ 92    # (a.x AND b.x) OR (a.y AND c.y)
+ 93    # only table a can be push down
+ 94    pushdown_tables = set()
+ 95
+ 96    for a in predicates:
+ 97        a_tables = set(exp.column_table_names(a))
+ 98
+ 99        for b in predicates:
+100            a_tables &= set(exp.column_table_names(b))
 101
-102    conditions = {}
+102        pushdown_tables.update(a_tables)
 103
-104    # for every pushdown table, find all related conditions in all predicates
-105    # combine them with ORS
-106    # (a.x AND and a.y AND b.x) OR (a.z AND c.y) -> (a.x AND a.y) OR (a.z)
-107    for table in sorted(pushdown_tables):
-108        for predicate in predicates:
-109            nodes = nodes_for_predicate(predicate, scope, scope_ref_count)
-110
-111            if table not in nodes:
-112                continue
-113
-114            predicate_condition = None
+104    conditions = {}
+105
+106    # for every pushdown table, find all related conditions in all predicates
+107    # combine them with ORS
+108    # (a.x AND and a.y AND b.x) OR (a.z AND c.y) -> (a.x AND a.y) OR (a.z)
+109    for table in sorted(pushdown_tables):
+110        for predicate in predicates:
+111            nodes = nodes_for_predicate(predicate, scope, scope_ref_count)
+112
+113            if table not in nodes:
+114                continue
 115
-116            for column in predicate.find_all(exp.Column):
-117                if column.table == table:
-118                    condition = column.find_ancestor(exp.Condition)
-119                    predicate_condition = (
-120                        exp.and_(predicate_condition, condition)
-121                        if predicate_condition
-122                        else condition
-123                    )
-124
-125            if predicate_condition:
-126                conditions[table] = (
-127                    exp.or_(conditions[table], predicate_condition)
-128                    if table in conditions
-129                    else predicate_condition
-130                )
-131
-132        for name, node in nodes.items():
-133            if name not in conditions:
-134                continue
-135
-136            predicate = conditions[name]
+116            predicate_condition = None
+117
+118            for column in predicate.find_all(exp.Column):
+119                if column.table == table:
+120                    condition = column.find_ancestor(exp.Condition)
+121                    predicate_condition = (
+122                        exp.and_(predicate_condition, condition)
+123                        if predicate_condition
+124                        else condition
+125                    )
+126
+127            if predicate_condition:
+128                conditions[table] = (
+129                    exp.or_(conditions[table], predicate_condition)
+130                    if table in conditions
+131                    else predicate_condition
+132                )
+133
+134        for name, node in nodes.items():
+135            if name not in conditions:
+136                continue
 137
-138            if isinstance(node, exp.Join):
-139                node.on(predicate, copy=False)
-140            elif isinstance(node, exp.Select):
-141                node.where(replace_aliases(node, predicate), copy=False)
+138            predicate = conditions[name]
+139
+140            if isinstance(node, exp.Join):
+141                node.on(predicate, copy=False)
+142            elif isinstance(node, exp.Select):
+143                node.where(replace_aliases(node, predicate), copy=False)
 
@@ -511,44 +515,44 @@ Additionally, we can't remove predicates from their original form.

-
144def nodes_for_predicate(predicate, sources, scope_ref_count):
-145    nodes = {}
-146    tables = exp.column_table_names(predicate)
-147    where_condition = isinstance(predicate.find_ancestor(exp.Join, exp.Where), exp.Where)
-148
-149    for table in tables:
-150        node, source = sources.get(table) or (None, None)
-151
-152        # if the predicate is in a where statement we can try to push it down
-153        # we want to find the root join or from statement
-154        if node and where_condition:
-155            node = node.find_ancestor(exp.Join, exp.From)
-156
-157        # a node can reference a CTE which should be pushed down
-158        if isinstance(node, exp.From) and not isinstance(source, exp.Table):
-159            with_ = source.parent.expression.args.get("with")
-160            if with_ and with_.recursive:
-161                return {}
-162            node = source.expression
-163
-164        if isinstance(node, exp.Join):
-165            if node.side and node.side != "RIGHT":
-166                return {}
-167            nodes[table] = node
-168        elif isinstance(node, exp.Select) and len(tables) == 1:
-169            # We can't push down window expressions
-170            has_window_expression = any(
-171                select for select in node.selects if select.find(exp.Window)
-172            )
-173            # we can't push down predicates to select statements if they are referenced in
-174            # multiple places.
-175            if (
-176                not node.args.get("group")
-177                and scope_ref_count[id(source)] < 2
-178                and not has_window_expression
-179            ):
-180                nodes[table] = node
-181    return nodes
+            
146def nodes_for_predicate(predicate, sources, scope_ref_count):
+147    nodes = {}
+148    tables = exp.column_table_names(predicate)
+149    where_condition = isinstance(predicate.find_ancestor(exp.Join, exp.Where), exp.Where)
+150
+151    for table in tables:
+152        node, source = sources.get(table) or (None, None)
+153
+154        # if the predicate is in a where statement we can try to push it down
+155        # we want to find the root join or from statement
+156        if node and where_condition:
+157            node = node.find_ancestor(exp.Join, exp.From)
+158
+159        # a node can reference a CTE which should be pushed down
+160        if isinstance(node, exp.From) and not isinstance(source, exp.Table):
+161            with_ = source.parent.expression.args.get("with")
+162            if with_ and with_.recursive:
+163                return {}
+164            node = source.expression
+165
+166        if isinstance(node, exp.Join):
+167            if node.side and node.side != "RIGHT":
+168                return {}
+169            nodes[table] = node
+170        elif isinstance(node, exp.Select) and len(tables) == 1:
+171            # We can't push down window expressions
+172            has_window_expression = any(
+173                select for select in node.selects if select.find(exp.Window)
+174            )
+175            # we can't push down predicates to select statements if they are referenced in
+176            # multiple places.
+177            if (
+178                not node.args.get("group")
+179                and scope_ref_count[id(source)] < 2
+180                and not has_window_expression
+181            ):
+182                nodes[table] = node
+183    return nodes
 
@@ -566,21 +570,21 @@ Additionally, we can't remove predicates from their original form.

-
184def replace_aliases(source, predicate):
-185    aliases = {}
-186
-187    for select in source.selects:
-188        if isinstance(select, exp.Alias):
-189            aliases[select.alias] = select.this
-190        else:
-191            aliases[select.name] = select
-192
-193    def _replace_alias(column):
-194        if isinstance(column, exp.Column) and column.name in aliases:
-195            return aliases[column.name].copy()
-196        return column
-197
-198    return predicate.transform(_replace_alias)
+            
186def replace_aliases(source, predicate):
+187    aliases = {}
+188
+189    for select in source.selects:
+190        if isinstance(select, exp.Alias):
+191            aliases[select.alias] = select.this
+192        else:
+193            aliases[select.name] = select
+194
+195    def _replace_alias(column):
+196        if isinstance(column, exp.Column) and column.name in aliases:
+197            return aliases[column.name].copy()
+198        return column
+199
+200    return predicate.transform(_replace_alias)
 
-- cgit v1.2.3