diff --git a/enginetest/queries/query_plans.go b/enginetest/queries/query_plans.go index 795b71189b..3850db6fda 100644 --- a/enginetest/queries/query_plans.go +++ b/enginetest/queries/query_plans.go @@ -22711,6 +22711,77 @@ WHERE keyless.c0 IN ( " └─ keys: xy.y\n" + "", }, + { + Query: `select x, u from xy xy_alias, lateral (select * from uv where xy_alias.y = u) uv;`, + ExpectedPlan: "Project\n" + + " ├─ columns: [xy_alias.x:0!null, uv.u:2!null]\n" + + " └─ LateralCrossJoin\n" + + " ├─ TableAlias(xy_alias)\n" + + " │ └─ ProcessTable\n" + + " │ └─ Table\n" + + " │ ├─ name: xy\n" + + " │ └─ columns: [x y]\n" + + " └─ SubqueryAlias\n" + + " ├─ name: uv\n" + + " ├─ outerVisibility: false\n" + + " ├─ isLateral: true\n" + + " ├─ cacheable: false\n" + + " ├─ colSet: (5,6)\n" + + " ├─ tableId: 3\n" + + " └─ Filter\n" + + " ├─ Eq\n" + + " │ ├─ xy_alias.y:1\n" + + " │ └─ uv.u:2!null\n" + + " └─ IndexedTableAccess(uv)\n" + + " ├─ index: [uv.u]\n" + + " ├─ keys: [xy_alias.y:1]\n" + + " ├─ colSet: (3,4)\n" + + " ├─ tableId: 2\n" + + " └─ Table\n" + + " ├─ name: uv\n" + + " └─ columns: [u v]\n" + + "", + ExpectedEstimates: "Project\n" + + " ├─ columns: [xy_alias.x, uv.u]\n" + + " └─ LateralCrossJoin (estimated cost=100999.000 rows=125)\n" + + " ├─ TableAlias(xy_alias)\n" + + " │ └─ Table\n" + + " │ └─ name: xy\n" + + " └─ SubqueryAlias\n" + + " ├─ name: uv\n" + + " ├─ outerVisibility: false\n" + + " ├─ isLateral: true\n" + + " ├─ cacheable: false\n" + + " ├─ colSet: (5,6)\n" + + " ├─ tableId: 3\n" + + " └─ Filter\n" + + " ├─ (xy_alias.y = uv.u)\n" + + " └─ IndexedTableAccess(uv)\n" + + " ├─ index: [uv.u]\n" + + " ├─ columns: [u v]\n" + + " └─ keys: xy_alias.y\n" + + "", + ExpectedAnalysis: "Project\n" + + " ├─ columns: [xy_alias.x, uv.u]\n" + + " └─ LateralCrossJoin (estimated cost=100999.000 rows=125) (actual rows=4 loops=1)\n" + + " ├─ TableAlias(xy_alias)\n" + + " │ └─ Table\n" + + " │ └─ name: xy\n" + + " └─ SubqueryAlias\n" + + " ├─ name: uv\n" + + " ├─ outerVisibility: false\n" + + " ├─ isLateral: true\n" + + " ├─ cacheable: false\n" + + " ├─ colSet: (5,6)\n" + + " ├─ tableId: 3\n" + + " └─ Filter\n" + + " ├─ (xy_alias.y = uv.u)\n" + + " └─ IndexedTableAccess(uv)\n" + + " ├─ index: [uv.u]\n" + + " ├─ columns: [u v]\n" + + " └─ keys: xy_alias.y\n" + + "", + }, { Query: `select x from xy where x > 0 and x <= 2 order by x`, ExpectedPlan: "IndexedTableAccess(xy)\n" + diff --git a/sql/analyzer/apply_indexes_from_outer_scope.go b/sql/analyzer/apply_indexes_from_outer_scope.go index f10c83ede9..33c7ebaf16 100644 --- a/sql/analyzer/apply_indexes_from_outer_scope.go +++ b/sql/analyzer/apply_indexes_from_outer_scope.go @@ -278,7 +278,7 @@ func tablesInScope(scope *plan.Scope) []string { } } for _, table := range scope.JoinSiblings() { - for name, _ := range getTablesByName(table) { + for name, _ := range getNamedChildren(table) { tables[name] = true } } diff --git a/sql/analyzer/assign_update_join.go b/sql/analyzer/assign_update_join.go index 096e70d964..2b67f8a465 100644 --- a/sql/analyzer/assign_update_join.go +++ b/sql/analyzer/assign_update_join.go @@ -51,7 +51,7 @@ func modifyUpdateExprsForJoin(ctx *sql.Context, a *Analyzer, n sql.Node, scope * // getUpdateTargetsByTable maps a set of table names and aliases to their corresponding update target Node func getUpdateTargetsByTable(node sql.Node, ij sql.Node, isJoin bool) (map[string]sql.Node, error) { namesOfTableToBeUpdated := plan.GetTablesToBeUpdated(node) - resolvedTables := getTablesByName(ij) + resolvedTables := getResolvedTablesByName(ij) updateTargets := make(map[string]sql.Node) for tableToBeUpdated, _ := range namesOfTableToBeUpdated { diff --git a/sql/analyzer/resolve_subqueries.go b/sql/analyzer/resolve_subqueries.go index c15136cf60..204b582f1d 100644 --- a/sql/analyzer/resolve_subqueries.go +++ b/sql/analyzer/resolve_subqueries.go @@ -36,7 +36,7 @@ func resolveSubqueries(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Sc } func addLeftTablesToScope(outerScope *plan.Scope, leftNode sql.Node) *plan.Scope { - resTbls := getTablesByName(leftNode) + resTbls := getNamedChildren(leftNode) subScope := outerScope for _, tbl := range resTbls { subScope = subScope.NewScopeInJoin(tbl) diff --git a/sql/analyzer/tables.go b/sql/analyzer/tables.go index 92b9a9b706..7b67f0d47f 100644 --- a/sql/analyzer/tables.go +++ b/sql/analyzer/tables.go @@ -97,9 +97,9 @@ func getResolvedTable(node sql.Node) *plan.ResolvedTable { return table } -// getTablesByName takes a node and returns all found resolved tables in a map. +// getResolvedTablesByName takes a node and returns all found resolved tables in a map. // This function will not look inside sql.OpaqueNodes (like plan.SubqueryAlias). -func getTablesByName(node sql.Node) map[string]*plan.ResolvedTable { +func getResolvedTablesByName(node sql.Node) map[string]*plan.ResolvedTable { ret := make(map[string]*plan.ResolvedTable) // TODO: We should change transform.Inspect to not walk the children of sql.OpaqueNodes (like transform.Node) // and add a transform.InspectWithOpaque that does. @@ -123,3 +123,17 @@ func getTablesByName(node sql.Node) map[string]*plan.ResolvedTable { }) return ret } + +func getNamedChildren(node sql.Node) map[string]sql.NameableNode { + ret := make(map[string]sql.NameableNode) + // TODO: We should change transform.Inspect to not walk the children of sql.OpaqueNodes (like transform.Node) + // and add a transform.InspectWithOpaque that does. + // Using transform.Node here achieves the same result without a large refactor. + transform.Node(node, func(node sql.Node) (sql.Node, transform.TreeIdentity, error) { + if nameable, ok := node.(sql.NameableNode); ok { + ret[strings.ToLower(nameable.Name())] = nameable + } + return nil, transform.SameTree, nil + }) + return ret +} diff --git a/sql/planbuilder/dml.go b/sql/planbuilder/dml.go index ef28377ac1..87f322156a 100644 --- a/sql/planbuilder/dml.go +++ b/sql/planbuilder/dml.go @@ -607,7 +607,7 @@ func hasJoinNode(node sql.Node) bool { func getResolvedTablesToUpdate(_ *sql.Context, node sql.Node, ij sql.Node) (resolvedTables []*plan.ResolvedTable, err error) { namesOfTablesToBeUpdated := plan.GetTablesToBeUpdated(node) - resolvedTablesMap := getTablesByName(ij) + resolvedTablesMap := getResolvedTablesByName(ij) for tableToBeUpdated, _ := range namesOfTablesToBeUpdated { resolvedTable, ok := resolvedTablesMap[strings.ToLower(tableToBeUpdated)] @@ -622,7 +622,7 @@ func getResolvedTablesToUpdate(_ *sql.Context, node sql.Node, ij sql.Node) (reso } // getTablesByName takes a node and returns all found resolved tables in a map. -func getTablesByName(node sql.Node) map[string]*plan.ResolvedTable { +func getResolvedTablesByName(node sql.Node) map[string]*plan.ResolvedTable { ret := make(map[string]*plan.ResolvedTable) transform.Inspect(node, func(node sql.Node) bool { diff --git a/sql/planbuilder/scalar.go b/sql/planbuilder/scalar.go index 72c7e7d68e..113432a964 100644 --- a/sql/planbuilder/scalar.go +++ b/sql/planbuilder/scalar.go @@ -1084,7 +1084,7 @@ func (b *Builder) ConvertVal(v *ast.SQLVal) sql.Expression { // filter, since we only need to load the tables once. All steps after this // one can assume that the expression has been fully resolved and is valid. func (b *Builder) buildMatchAgainst(inScope *scope, v *ast.MatchExpr) *expression.MatchAgainst { - rts := getTablesByName(inScope.node) + rts := getResolvedTablesByName(inScope.node) var rt *plan.ResolvedTable var matchTable string cols := make([]*expression.GetField, len(v.Columns))