Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -50,12 +51,25 @@ public final class LogicalPlanFragment {
@Nonnull
private Optional<State> state;

@Nonnull
private final List<Expression> innerJoinExpressions;

private LogicalPlanFragment(@Nonnull Optional<LogicalPlanFragment> parent,
@Nonnull LogicalOperators operators,
@Nonnull Optional<State> 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<Expression> getInnerJoinExpressions() {
return Collections.unmodifiableList(innerJoinExpressions);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -743,7 +743,7 @@ public NonnullPair<String, CompatibleTypeEvolutionPredicate.FieldAccessTrieNode>
return expressionVisitor.visitInlineTableDefinition(ctx);
}

@Nonnull
@Nullable
@Override
public Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx) {
return visitChildren(ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -596,7 +596,7 @@ public NonnullPair<String, CompatibleTypeEvolutionPredicate.FieldAccessTrieNode>
}


@Nonnull
@Nullable
@Override
public Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx) {
return getDelegate().visitInnerJoin(ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderByExpression> orderBys = List.of();
if (simpleTableContext.groupByClause() != null || hasAggregations(simpleTableContext.selectElements())) {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ public interface TypedVisitor extends RelationalParserVisitor<Object> {
@Nullable
Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx);

@Nonnull
@Nullable
@Override
LogicalOperator visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx);
Void visitTableSourceBase(@Nonnull RelationalParser.TableSourceBaseContext ctx);

@Nonnull
@Override
Expand Down Expand Up @@ -345,7 +345,7 @@ public interface TypedVisitor extends RelationalParserVisitor<Object> {
@Override
NonnullPair<String, CompatibleTypeEvolutionPredicate.FieldAccessTrieNode> visitInlineTableDefinition(RelationalParser.InlineTableDefinitionContext ctx);

@Nonnull
@Nullable
@Override
Object visitInnerJoin(@Nonnull RelationalParser.InnerJoinContext ctx);

Expand Down
132 changes: 132 additions & 0 deletions yaml-tests/src/test/resources/join-tests.yamsql
Original file line number Diff line number Diff line change
Expand Up @@ -57,72 +57,192 @@ 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';
- unorderedResult: [{"Jack", "Williams"},
{"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
(select fname, lname, dept_id from emp where exists (select * from project where emp_id = emp.id)) as sq,
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)
Expand Down Expand Up @@ -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)
Expand All @@ -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"
...
Loading