diff --git a/enginetest/queries/join_queries.go b/enginetest/queries/join_queries.go index f08261f3ec..87b4d845e0 100644 --- a/enginetest/queries/join_queries.go +++ b/enginetest/queries/join_queries.go @@ -1342,6 +1342,50 @@ var JoinScriptTests = []ScriptTest{ }, }, }, + { + // https://github.com/dolthub/dolt/issues/10304 + Name: "3-way join with 1 primary key table, 2 keyless tables, and join filter on keyless tables", + SetUpScript: []string{ + "CREATE TABLE t0(c0 VARCHAR(500), c1 INT);", + "CREATE TABLE t6(t6c0 VARCHAR(500), t6c1 INT, PRIMARY KEY(t6c0));", + "CREATE VIEW v0(c0) AS SELECT t0.c1 FROM t0;", + "create table t1(c0 int)", + "INSERT INTO t6(t6c0) VALUES (3);", + "INSERT INTO t6(t6c0, t6c1) VALUES (2, '-1'), ('', '1');", + "INSERT INTO t6(t6c0, t6c1) VALUES (true, 0);", + "INSERT INTO t0(c0, c1) VALUES (-1, false);", + "INSERT INTO t0(c1) VALUES (-7);", + "insert into t1(c0) values (-7),(0)", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "SELECT * FROM t6, t1 INNER JOIN t0 ON ((t1.c0)<=>(t0.c1));", + Expected: []sql.Row{ + {"", 1, -7, nil, -7}, + {"", 1, 0, "-1", 0}, + {"1", 0, -7, nil, -7}, + {"1", 0, 0, "-1", 0}, + {"2", -1, -7, nil, -7}, + {"2", -1, 0, "-1", 0}, + {"3", nil, -7, nil, -7}, + {"3", nil, 0, "-1", 0}, + }, + }, + { + Query: "SELECT * FROM t6, v0 INNER JOIN t0 ON ((v0.c0)<=>(t0.c1));", + Expected: []sql.Row{ + {"", 1, -7, nil, -7}, + {"", 1, 0, "-1", 0}, + {"1", 0, -7, nil, -7}, + {"1", 0, 0, "-1", 0}, + {"2", -1, -7, nil, -7}, + {"2", -1, 0, "-1", 0}, + {"3", nil, -7, nil, -7}, + {"3", nil, 0, "-1", 0}, + }, + }, + }, + }, } var LateralJoinScriptTests = []ScriptTest{ diff --git a/sql/memo/join_order_builder.go b/sql/memo/join_order_builder.go index 34726ef4a5..7d6d835f80 100644 --- a/sql/memo/join_order_builder.go +++ b/sql/memo/join_order_builder.go @@ -254,7 +254,7 @@ func (j *joinOrderBuilder) buildSingleLookupPlan() bool { fds := j.m.root.RelProps.FuncDeps() fdKey, hasKey := fds.StrictKey() // fdKey is a set of columns which constrain all other columns in the join. - // If a chain of lookups exist, then the columns in fdKey must be in the innermost join. + // If a chain of lookups exists, then the columns in fdKey must be in the innermost join. if !hasKey { return false } @@ -324,22 +324,14 @@ func (j *joinOrderBuilder) buildSingleLookupPlan() bool { } } - if len(joinCandidates) > 1 { - // We end up here if there are multiple possible choices for the next join. - // This could happen if there are redundant rules. For now, we bail out if this happens. - return false - } - - if len(joinCandidates) == 0 { - // There are still tables left to join, but no more filters that match the already joined tables. - // This can happen, for instance, if the remaining table is a single-row table that was cross-joined. - // It's probably safe to just join the remaining tables here. - remainingVertexes := j.allVertices().difference(currentlyJoinedVertexes) - for idx, ok := remainingVertexes.next(0); ok; idx, ok = remainingVertexes.next(idx + 1) { - nextVertex := newBitSet(idx) - j.addJoin(plan.JoinTypeCross, currentlyJoinedVertexes, nextVertex, nil, nil, false) - currentlyJoinedVertexes = currentlyJoinedVertexes.union(nextVertex) - } + if len(joinCandidates) != 1 { + // For now, we bail out if there are no or multiple possible choices for the next join. + // There are no possible choices for the next join when the filters are not applicable to the table + // containing the functional dependency key. Suppose we have a query like + // `select from A, B, inner join C on B.c0 <=> C.c0` where table A has a primary key and tables B and C are + // keyless; in this case, keyColumn would match table A's primary key, currentlyJoinedTables would only + // contain table A, and the join filter for tables B and C would not apply. + // There are multiple possible choices for the next join if there are redundant rules. return false }