diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalPlanFragment.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalPlanFragment.java index 1975007f03..f4801f705a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalPlanFragment.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalPlanFragment.java @@ -21,16 +21,17 @@ package com.apple.foundationdb.relational.recordlayer.query; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -50,12 +51,25 @@ public final class LogicalPlanFragment { @Nonnull private Optional state; + @Nonnull + private final List innerJoinExpressions; + private LogicalPlanFragment(@Nonnull Optional parent, @Nonnull LogicalOperators operators, @Nonnull Optional state) { this.parent = parent; this.operators = operators; this.state = state; + this.innerJoinExpressions = new ArrayList<>(); + } + + public void addInnerJoinExpression(@Nonnull Expression joinExpression) { + this.innerJoinExpressions.add(joinExpression); + } + + @Nonnull + public List getInnerJoinExpressions() { + return Collections.unmodifiableList(innerJoinExpressions); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java index c318a443b3..138cd2bc4c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java @@ -696,9 +696,9 @@ public Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx) return queryVisitor.visitTableSources(ctx); } - @Nonnull + @Nullable @Override - public LogicalOperator visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { + public Void visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { return queryVisitor.visitTableSourceBase(ctx); } @@ -743,7 +743,7 @@ public NonnullPair return expressionVisitor.visitInlineTableDefinition(ctx); } - @Nonnull + @Nullable @Override public Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx) { return visitChildren(ctx); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java index 4273b248ac..25b241ee8c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java @@ -548,9 +548,9 @@ public Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx) return getDelegate().visitTableSources(ctx); } - @Nonnull + @Nullable @Override - public LogicalOperator visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { + public Void visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { return getDelegate().visitTableSourceBase(ctx); } @@ -596,7 +596,7 @@ public NonnullPair } - @Nonnull + @Nullable @Override public Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx) { return getDelegate().visitInnerJoin(ctx); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java index 9d7a45dd49..c16f38a206 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java @@ -222,6 +222,11 @@ public LogicalOperator visitSimpleTable(@Nonnull RelationalParser.SimpleTableCon var where = Optional.ofNullable(simpleTableContext.fromClause().whereExpr() == null ? null : visitWhereExpr(simpleTableContext.fromClause().whereExpr())); + + for (final var expression : getDelegate().getCurrentPlanFragment().getInnerJoinExpressions()) { + where = where.map(e -> getDelegate().resolveFunction("and", e, expression)).or(() -> Optional.of(expression)); + } + Expressions selectExpressions; List orderBys = List.of(); if (simpleTableContext.groupByClause() != null || hasAggregations(simpleTableContext.selectElements())) { @@ -312,17 +317,28 @@ public Void visitFromClause(@Nonnull RelationalParser.FromClauseContext fromClau @Override public Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx) { for (final var tableSource : ctx.tableSource()) { - final var logicalOperator = Assert.castUnchecked(tableSource.accept(this), LogicalOperator.class); - getDelegate().getCurrentPlanFragment().addOperator(logicalOperator); + tableSource.accept(this); } return null; } - @Nonnull + @Nullable + @Override + public Void visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { + getDelegate().getCurrentPlanFragment().addOperator(Assert.castUnchecked(ctx.tableSourceItem().accept(this), LogicalOperator.class)); + for (final var joinPart : ctx.joinPart()) { + joinPart.accept(this); + } + return null; + } + + @Nullable @Override - public LogicalOperator visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx) { - Assert.thatUnchecked(ctx.joinPart().isEmpty(), "explicit join types are not supported"); - return Assert.castUnchecked(ctx.tableSourceItem().accept(this), LogicalOperator.class); + public Void visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx) { + Assert.isNullUnchecked(ctx.uidList(), ErrorCode.UNSUPPORTED_QUERY, "using is not yet supported for inner join"); + getDelegate().getCurrentPlanFragment().addOperator(Assert.castUnchecked(ctx.tableSourceItem().accept(this), LogicalOperator.class)); + getDelegate().getCurrentPlanFragment().addInnerJoinExpression(Assert.castUnchecked(ctx.expression().accept(this), Expression.class)); + return null; } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java index 92a0ca8db9..5ba0b71665 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java @@ -314,9 +314,9 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nullable Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx); - @Nonnull + @Nullable @Override - LogicalOperator visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx); + Void visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx); @Nonnull @Override @@ -345,7 +345,7 @@ public interface TypedVisitor extends RelationalParserVisitor { @Override NonnullPair visitInlineTableDefinition(RelationalParser.InlineTableDefinitionContext ctx); - @Nonnull + @Nullable @Override Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx); diff --git a/yaml-tests/src/test/resources/join-tests.yamsql b/yaml-tests/src/test/resources/join-tests.yamsql index fa2d324932..67477a437a 100644 --- a/yaml-tests/src/test/resources/join-tests.yamsql +++ b/yaml-tests/src/test/resources/join-tests.yamsql @@ -57,6 +57,34 @@ test_block: - - query: select sq2.y, sq1.x from (select ida as x from a) sq1, (select idb as y from b) sq2; - result: [{!l 4, !l 1}] + - + # simple inner join + - query: select fname, lname, name from emp inner join dept on emp.dept_id = dept.id; + - supported_version: !current_version + - unorderedResult: [ + {'Jack', 'Williams', 'Engineering'}, + {'Thomas', 'Johnson', 'Engineering'}, + {'Emily', 'Martinez', 'Engineering'}, + {'Amelia', 'Johnson', 'Engineering'}, + {'Daniel', 'Miller', 'Sales'}, + {'Chloe', 'Jones', 'Sales'}, + {'Charlotte', 'Garcia', 'Sales'}, + {'Megan', 'Miller', 'Marketing'}, + {'Harry', 'Smith', 'Marketing'}] + - + # as previous with table names + - query: select emp.fname, emp.lname, dept.name from emp inner join dept on emp.dept_id = dept.id; + - supported_version: !current_version + - unorderedResult: [ + {'Jack', 'Williams', 'Engineering'}, + {'Thomas', 'Johnson', 'Engineering'}, + {'Emily', 'Martinez', 'Engineering'}, + {'Amelia', 'Johnson', 'Engineering'}, + {'Daniel', 'Miller', 'Sales'}, + {'Chloe', 'Jones', 'Sales'}, + {'Charlotte', 'Garcia', 'Sales'}, + {'Megan', 'Miller', 'Marketing'}, + {'Harry', 'Smith', 'Marketing'}] - # Get names of people working in Engineering: - query: select fname, lname from emp, dept where emp.dept_id = dept.id and dept.name = 'Engineering'; @@ -64,12 +92,28 @@ test_block: {"Thomas", "Johnson"}, {"Emily", "Martinez"}, {"Amelia", "Johnson"}] + - + # inner join version of previous + - query: select fname, lname from emp inner join dept on emp.dept_id = dept.id and dept.name = 'Engineering'; + - supported_version: !current_version + - unorderedResult: [{"Jack", "Williams"}, + {"Thomas", "Johnson"}, + {"Emily", "Martinez"}, + {"Amelia", "Johnson"}] - # Get names of people working on a project - query: select fname, lname from emp where exists (select * from project where emp_id = emp.id); - unorderedResult: [{"Emily", "Martinez"}, {"Daniel", "Miller"}, {"Megan", "Miller"}] + - + # inner join version of previous + - query: select fname, lname from emp inner join project on project.emp_id = emp.id; + - supported_version: !current_version + - unorderedResult: [{"Emily", "Martinez"}, + {"Daniel", "Miller"}, + {"Megan", "Miller"}] + - # Get names of people working on a project in Sales department - query: select fname, lname from @@ -77,52 +121,128 @@ test_block: dept where sq.dept_id = dept.id and dept.name = 'Sales'; - unorderedResult: [{"Daniel", "Miller"}] + - + # inner join version of previous + - query: select fname, lname from emp inner join project on emp_id = emp.id inner join dept on dept_id = dept.id and dept.name = 'Sales'; + - supported_version: !current_version + - unorderedResult: [{"Daniel", "Miller"}] + - # three-way join to find which departments' corresponding projects. - query: select dept.name, project.name from emp, dept, project where emp.dept_id = dept.id and project.emp_id = emp.id; - unorderedResult: [{"Engineering", "OLAP"}, {"Sales", "Feedback"}, {"Marketing", "SEO"}] + - + # inner join version of previous + - query: select dept.name, project.name from emp inner join dept on emp.dept_id = dept.id inner join project on project.emp_id = emp.id; + - supported_version: !current_version + - unorderedResult: [{"Engineering", "OLAP"}, + {"Sales", "Feedback"}, + {"Marketing", "SEO"}] + - # two-level left-nested join to find which departments' corresponding projects. - query: select sq.name, project.name from (select dept.name, emp.id from emp, dept where emp.dept_id = dept.id) as sq, project where project.emp_id = sq.id; - unorderedResult: [{"Engineering", "OLAP"}, {"Sales", "Feedback"}, {"Marketing", "SEO"}] + - + # inner join version of previous #1 + - query: select sq.name, project.name from (select dept.name, emp.id from emp, dept where emp.dept_id = dept.id) as sq inner join project on project.emp_id = sq.id; + - supported_version: !current_version + - unorderedResult: [{"Engineering", "OLAP"}, + {"Sales", "Feedback"}, + {"Marketing", "SEO"}] + - + # inner join version of previous #2 + - query: select sq.name, project.name from project inner join (select dept.name, emp.id from emp, dept where emp.dept_id = dept.id) as sq on project.emp_id = sq.id; + - supported_version: !current_version + - unorderedResult: [{"Engineering", "OLAP"}, + {"Sales", "Feedback"}, + {"Marketing", "SEO"}] + - + # inner join version of previous #3 + - query: select dept.name, project.name from emp inner join dept on emp.dept_id = dept.id inner join project on project.emp_id = emp.id; + - supported_version: !current_version + - unorderedResult: [{"Engineering", "OLAP"}, + {"Sales", "Feedback"}, + {"Marketing", "SEO"}] - # two-level right-nested join to find which departments' corresponding projects. - query: select sq.name, project.name from project, (select dept.name, emp.id from emp, dept where emp.dept_id = dept.id) as sq where project.emp_id = sq.id; - unorderedResult: [{"Engineering", "OLAP"}, {"Sales", "Feedback"}, {"Marketing", "SEO"}] + - + # inner join version of previous + - query: select dept.name, project.name from project inner join emp on emp.id = project.emp_id inner join dept on emp.dept_id = dept.id; + - supported_version: !current_version + - unorderedResult: [{"Engineering", "OLAP"}, + {"Sales", "Feedback"}, + {"Marketing", "SEO"}] - # Join with NOT IN - get employees in Engineering excluding specific employee ids - query: select emp.id, fname, lname from emp, dept where emp.dept_id = dept.id and dept.name = 'Engineering' and emp.id not in (1, 3) - unorderedResult: [{ID: 2, FNAME: "Thomas", LNAME: "Johnson"}, {ID: 4, FNAME: "Amelia", LNAME: "Johnson"}] + - + # inner join version of previous + - query: select emp.id, fname, lname from emp inner join dept on emp.dept_id = dept.id and dept.name = 'Engineering' and emp.id not in (1, 3) + - supported_version: !current_version + - unorderedResult: [{ID: 2, FNAME: "Thomas", LNAME: "Johnson"}, + {ID: 4, FNAME: "Amelia", LNAME: "Johnson"}] - # Join with NOT IN - get projects excluding certain project ids - query: select project.id, project.name from emp, project where project.emp_id = emp.id and project.id not in (1, 2) - unorderedResult: [{ID: 3, NAME: "Feedback"}] + - + # inner join version of previous + - query: select project.id, project.name from emp inner join project on project.emp_id = emp.id where project.id not in (1, 2) + - supported_version: !current_version + - unorderedResult: [{ID: 3, NAME: "Feedback"}] - # Three-way join with NOT IN - departments with projects, excluding specific departments - query: select dept.id, dept.name, project.name from emp, dept, project where emp.dept_id = dept.id and project.emp_id = emp.id and dept.id not in (1) - unorderedResult: [{ID: 2, "Sales", "Feedback"}, {ID: 3, "Marketing", "SEO"}] + - + # inner join version of previous + - query: select dept.id, dept.name, project.name from emp inner join dept on emp.dept_id = dept.id and dept.id not in (1) inner join project on project.emp_id = emp.id + - supported_version: !current_version + - unorderedResult: [{ID: 2, "Sales", "Feedback"}, + {ID: 3, "Marketing", "SEO"}] - # Join with NOT IN - employees in Sales department excluding specific ids - query: select emp.id from emp, dept where emp.dept_id = dept.id and dept.id = 2 and emp.id not in (5, 6) - unorderedResult: [{7}] + - + # inner join version of previous + - query: select emp.id from emp inner join dept on emp.dept_id = dept.id and dept.id = 2 and emp.id not in (5, 6) + - supported_version: !current_version + - unorderedResult: [{7}] - # Join with NOT IN - all employees except those in excluded departments - query: select emp.id, fname from emp, dept where emp.dept_id = dept.id and dept.id not in (1, 3) - unorderedResult: [{ID: 5, FNAME: "Daniel"}, {ID: 6, FNAME: "Chloe"}, {ID: 7, FNAME: "Charlotte"}] + - + # inner join version of previous + - query: select emp.id, fname from emp inner join dept on emp.dept_id = dept.id where dept.id not in (1, 3) + - supported_version: !current_version + - unorderedResult: [{ID: 5, FNAME: "Daniel"}, + {ID: 6, FNAME: "Chloe"}, + {ID: 7, FNAME: "Charlotte"}] - # Join with NOT IN on non-existent values (should return all matching rows) - query: select emp.id from emp, dept where emp.dept_id = dept.id and dept.id = 1 and emp.id not in (10, 20, 30) - unorderedResult: [{1}, {2}, {3}, {4}] + - + # inner join version of previous + - query: select emp.id from emp inner join dept on emp.dept_id = dept.id and dept.id = 1 and emp.id not in (10, 20, 30) + - supported_version: !current_version + - unorderedResult: [{1}, {2}, {3}, {4}] - # Subquery join with NOT IN - query: select fname, lname from (select fname, lname, emp.id from emp, dept where emp.dept_id = dept.id and dept.name = 'Engineering') as sq where sq.id not in (1, 4) @@ -168,6 +288,12 @@ test_block: - query: select fname, lname from (select fname, lname, emp.id from emp, dept where emp.dept_id = dept.id and dept.name = 'Engineering') as sq where sq.id in (1, 4) - unorderedResult: [{"Jack", "Williams"}, {"Amelia", "Johnson"}] + - + # inner join version of previous + - query: select fname, lname from (select fname, lname, emp.id from emp inner join dept on emp.dept_id = dept.id and dept.name = 'Engineering') as sq where sq.id in (1, 4) + - supported_version: !current_version + - unorderedResult: [{"Jack", "Williams"}, + {"Amelia", "Johnson"}] - # Join with both IN and NOT IN conditions - query: select emp.id, fname from emp, dept where emp.dept_id = dept.id and dept.id in (1, 2) and emp.id not in (1, 5) @@ -180,4 +306,10 @@ test_block: # Join with IN on non-existent values (should return empty) - query: select emp.id from emp, dept where emp.dept_id = dept.id and dept.id = 1 and emp.id in (10, 20, 30) - result: [] + + - + # inner join with using + - query: select fname, lname, name from emp inner join dept using (fname); + - supported_version: !current_version + - error: "0AF00" ...