/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.presto.operator; import com.facebook.presto.RowPagesBuilder; import com.facebook.presto.operator.NestedLoopBuildOperator.NestedLoopBuildOperatorFactory; import com.facebook.presto.operator.NestedLoopJoinOperator.NestedLoopJoinOperatorFactory; import com.facebook.presto.operator.NestedLoopJoinOperator.NestedLoopPageBuilder; import com.facebook.presto.spi.Page; import com.facebook.presto.spi.type.Type; import com.facebook.presto.sql.planner.plan.PlanNodeId; import com.facebook.presto.testing.MaterializedResult; import com.facebook.presto.testing.TestingTaskContext; import com.google.common.collect.ImmutableList; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.util.List; import java.util.concurrent.ExecutorService; import static com.facebook.presto.RowPagesBuilder.rowPagesBuilder; import static com.facebook.presto.SessionTestUtils.TEST_SESSION; import static com.facebook.presto.operator.OperatorAssertion.assertOperatorEquals; import static com.facebook.presto.operator.ValuesOperator.ValuesOperatorFactory; import static com.facebook.presto.spi.type.BigintType.BIGINT; import static com.facebook.presto.spi.type.VarcharType.VARCHAR; import static com.facebook.presto.testing.MaterializedResult.resultBuilder; import static com.google.common.collect.Iterables.concat; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestNestedLoopJoinOperator { private ExecutorService executor; @BeforeClass public void setUp() { executor = newCachedThreadPool(daemonThreadsNamed("test-%s")); } @AfterClass public void tearDown() { executor.shutdownNow(); } @Test public void testNestedLoopJoin() throws Exception { TaskContext taskContext = createTaskContext(); // build RowPagesBuilder buildPages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT, BIGINT)) .addSequencePage(3, 20, 30, 40); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe RowPagesBuilder probePages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT, BIGINT)); List<Page> probeInput = probePages .addSequencePage(2, 0, 1000, 2000) .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR, BIGINT, BIGINT)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probePages.getTypes(), buildPages.getTypes())) .row("0", 1000L, 2000L, "20", 30L, 40L) .row("0", 1000L, 2000L, "21", 31L, 41L) .row("0", 1000L, 2000L, "22", 32L, 42L) .row("1", 1001L, 2001L, "20", 30L, 40L) .row("1", 1001L, 2001L, "21", 31L, 41L) .row("1", 1001L, 2001L, "22", 32L, 42L) .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); // Test probe pages has more rows buildPages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT, BIGINT)) .addSequencePage(2, 20, 30, 40); nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR, BIGINT, BIGINT)); // probe probePages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT, BIGINT)); probeInput = probePages .addSequencePage(3, 0, 1000, 2000) .build(); // expected expected = resultBuilder(taskContext.getSession(), concat(probePages.getTypes(), buildPages.getTypes())) .row("0", 1000L, 2000L, "20", 30L, 40L) .row("1", 1001L, 2001L, "20", 30L, 40L) .row("2", 1002L, 2002L, "20", 30L, 40L) .row("0", 1000L, 2000L, "21", 31L, 41L) .row("1", 1001L, 2001L, "21", 31L, 41L) .row("2", 1002L, 2002L, "21", 31L, 41L) .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testCrossJoinWithNullProbe() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("a") .row("b"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("A") .row((String) null) .row((String) null) .row("A") .row("B") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("A", "a") .row(null, "a") .row(null, "a") .row("A", "a") .row("B", "a") .row("A", "b") .row(null, "b") .row(null, "b") .row("A", "b") .row("B", "b") .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testCrossJoinWithNullBuild() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("a") .row((String) null) .row((String) null) .row("a") .row("b"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("A") .row("B") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("A", "a") .row("A", null) .row("A", null) .row("A", "a") .row("A", "b") .row("B", "a") .row("B", null) .row("B", null) .row("B", "a") .row("B", "b") .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testCrossJoinWithNullOnBothSides() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("a") .row((String) null) .row("b") .row("c") .row((String) null); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("A") .row("B") .row((String) null) .row("C") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("A", "a") .row("A", null) .row("A", "b") .row("A", "c") .row("A", null) .row("B", "a") .row("B", null) .row("B", "b") .row("B", "c") .row("B", null) .row(null, "a") .row(null, null) .row(null, "b") .row(null, "c") .row(null, null) .row("C", "a") .row("C", null) .row("C", "b") .row("C", "c") .row("C", null) .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testBuildMultiplePages() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("a") .pageBreak() .row((String) null) .row("b") .row("c") .pageBreak() .row("d"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("A") .row("B") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("A", "a") .row("B", "a") .row("A", null) .row("A", "b") .row("A", "c") .row("B", null) .row("B", "b") .row("B", "c") .row("A", "d") .row("B", "d") .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testProbeMultiplePages() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("A") .row("B"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("a") .pageBreak() .row((String) null) .row("b") .row("c") .pageBreak() .row("d") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("a", "A") .row("a", "B") .row(null, "A") .row("b", "A") .row("c", "A") .row(null, "B") .row("b", "B") .row("c", "B") .row("d", "A") .row("d", "B") .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testProbeAndBuildMultiplePages() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("A") .row("B") .pageBreak() .row("C"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("a") .pageBreak() .row((String) null) .row("b") .row("c") .pageBreak() .row("d") .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .row("a", "A") .row("a", "B") .row("a", "C") .row(null, "A") .row("b", "A") .row("c", "A") .row(null, "B") .row("b", "B") .row("c", "B") .row(null, "C") .row("b", "C") .row("c", "C") .row("d", "A") .row("d", "B") .row("d", "C") .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testEmptyProbePage() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .row("A") .row("B") .pageBreak() .row("C"); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .pageBreak() .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testEmptyBuildPage() throws Exception { TaskContext taskContext = createTaskContext(); // build List<Type> buildTypes = ImmutableList.of(VARCHAR); RowPagesBuilder buildPages = rowPagesBuilder(buildTypes) .pageBreak(); NestedLoopJoinPagesSupplier nestedLoopJoinPagesSupplier = buildPageSource(taskContext, buildPages); // probe List<Type> probeTypes = ImmutableList.of(VARCHAR); RowPagesBuilder probePages = rowPagesBuilder(probeTypes); List<Page> probeInput = probePages .row("A") .row("B") .pageBreak() .build(); NestedLoopJoinOperatorFactory joinOperatorFactory = new NestedLoopJoinOperatorFactory(3, new PlanNodeId("test"), nestedLoopJoinPagesSupplier, ImmutableList.of(VARCHAR)); // expected MaterializedResult expected = resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypes())) .build(); assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected); } @Test public void testCount() throws Exception { // normal case Page buildPage = new Page(100); Page probePage = new Page(45); NestedLoopPageBuilder resultPageBuilder = new NestedLoopPageBuilder(probePage, buildPage); assertTrue(resultPageBuilder.hasNext(), "There should be at least one page."); long result = 0; while (resultPageBuilder.hasNext()) { result += resultPageBuilder.next().getPositionCount(); } assertEquals(result, 4500); // force the product to be bigger than Integer.MAX_VALUE buildPage = new Page(Integer.MAX_VALUE - 10); resultPageBuilder = new NestedLoopPageBuilder(probePage, buildPage); result = 0; while (resultPageBuilder.hasNext()) { result += resultPageBuilder.next().getPositionCount(); } assertEquals((Integer.MAX_VALUE - 10) * 45L, result); } private TaskContext createTaskContext() { return TestingTaskContext.createTaskContext(executor, TEST_SESSION); } private static NestedLoopJoinPagesSupplier buildPageSource(TaskContext taskContext, RowPagesBuilder buildPages) { DriverContext driverContext = taskContext.addPipelineContext(0, true, true).addDriverContext(); ValuesOperatorFactory valuesOperatorFactory = new ValuesOperatorFactory(0, new PlanNodeId("test"), buildPages.getTypes(), buildPages.build()); NestedLoopBuildOperatorFactory nestedLoopBuildOperatorFactory = new NestedLoopBuildOperatorFactory(1, new PlanNodeId("test"), buildPages.getTypes()); Driver driver = new Driver(driverContext, valuesOperatorFactory.createOperator(driverContext), nestedLoopBuildOperatorFactory.createOperator(driverContext)); valuesOperatorFactory.close(); nestedLoopBuildOperatorFactory.close(); while (!driver.isFinished()) { driver.process(); } return nestedLoopBuildOperatorFactory.getNestedLoopJoinPagesSupplier(); } }