Skip to content

Commit e324f45

Browse files
committed
Merge branch 'optimize-join' into cte-support
# Conflicts: # mindsdb_sql/planner/plan_join.py
2 parents c5cb9df + ef30b12 commit e324f45

1 file changed

Lines changed: 79 additions & 3 deletions

File tree

mindsdb_sql/planner/plan_join.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TableInfo:
2121
sub_select: ast.ASTNode = None
2222
predictor_info: dict = None
2323
join_condition = None
24-
24+
index: int = None
2525

2626
class PlanJoin:
2727

@@ -85,12 +85,15 @@ def __init__(self, planner):
8585

8686
# index to lookup tables
8787
self.tables_idx = None
88+
self.tables = []
89+
self.tables_fetch_step = {}
8890

8991
self.step_stack = None
9092
self.query_context = {}
9193

9294
self.partition = None
9395

96+
9497
def plan(self, query):
9598
self.tables_idx = {}
9699
join_step = self.plan_join_tables(query)
@@ -146,7 +149,8 @@ def resolve_table(self, table):
146149
return TableInfo(integration, table, aliases, conditions=[], sub_select=sub_select)
147150

148151
def get_table_for_column(self, column: Identifier):
149-
152+
if not isinstance(column, Identifier):
153+
return
150154
# to lowercase
151155
parts = tuple(map(str.lower, column.parts[:-1]))
152156
if parts in self.tables_idx:
@@ -161,6 +165,9 @@ def get_join_sequence(self, node, condition=None):
161165
for alias in table_info.aliases:
162166
self.tables_idx[alias] = table_info
163167

168+
table_info.index = len(self.tables)
169+
self.tables.append(table_info)
170+
164171
table_info.predictor_info = self.planner.get_predictor(node)
165172

166173
if condition is not None:
@@ -378,13 +385,16 @@ def process_table(self, item, query_in):
378385
# not use conditions
379386
conditions = []
380387

388+
conditions += self.get_filters_from_join_conditions(item)
389+
381390
if self.query_context['use_limit']:
382391
order_by = None
383392
if query_in.order_by is not None:
384393
order_by = []
385394
# all order column be from this table
386395
for col in query_in.order_by:
387-
if self.get_table_for_column(col.field).table != item.table:
396+
table_info = self.get_table_for_column(col.field)
397+
if table_info is None or table_info.table != item.table:
388398
order_by = False
389399
break
390400
col = copy.deepcopy(col)
@@ -408,6 +418,8 @@ def process_table(self, item, query_in):
408418
query2.where = cond
409419

410420
step = self.planner.get_integration_select_step(query2)
421+
self.tables_fetch_step[item.index] = step
422+
411423
self.add_plan_step(step)
412424
self.step_stack.append(step)
413425

@@ -442,6 +454,70 @@ def _check_conditions(node, **kwargs):
442454
query_traversal(model_table.join_condition, _check_conditions)
443455
return columns_map
444456

457+
def get_filters_from_join_conditions(self, fetch_table):
458+
459+
binary_ops = set()
460+
conditions = []
461+
data_conditions = []
462+
463+
def _check_conditions(node, **kwargs):
464+
if not isinstance(node, BinaryOperation):
465+
return
466+
467+
if node.op != '=':
468+
binary_ops.add(node.op.lower())
469+
return
470+
471+
arg1, arg2 = node.args
472+
table1 = self.get_table_for_column(arg1) if isinstance(arg1, Identifier) else None
473+
table2 = self.get_table_for_column(arg2) if isinstance(arg2, Identifier) else None
474+
475+
if table1 is not fetch_table:
476+
if table2 is not fetch_table:
477+
return
478+
# set our table first
479+
table1, table2 = table2, table1
480+
arg1, arg2 = arg2, arg1
481+
482+
if isinstance(arg2, Constant):
483+
conditions.append(node)
484+
elif table2 is not None:
485+
data_conditions.append([arg1, arg2])
486+
487+
query_traversal(fetch_table.join_condition, _check_conditions)
488+
489+
binary_ops.discard('and')
490+
if len(binary_ops) > 0:
491+
# other operations exists, skip
492+
return []
493+
494+
for arg1, arg2 in data_conditions:
495+
# is fetched?
496+
table2 = self.get_table_for_column(arg2)
497+
fetch_step = self.tables_fetch_step.get(table2.index)
498+
499+
if fetch_step is None:
500+
continue
501+
502+
# extract distinct values
503+
# remove aliases
504+
arg1 = Identifier(parts=[arg1.parts[-1]])
505+
arg2 = Identifier(parts=[arg2.parts[-1]])
506+
507+
query2 = Select(targets=[arg2], distinct=True)
508+
subselect_step = SubSelectStep(query2, fetch_step.result)
509+
subselect_step = self.add_plan_step(subselect_step)
510+
511+
conditions.append(BinaryOperation(
512+
op='in',
513+
args=[
514+
arg1,
515+
Parameter(subselect_step.result)
516+
]
517+
))
518+
519+
return conditions
520+
445521
def process_predictor(self, item, query_in):
446522
if len(self.step_stack) == 0:
447523
raise NotImplementedError("Predictor can't be first element of join syntax")

0 commit comments

Comments
 (0)