| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.drill.exec.physical.impl.join; |
| |
| import org.apache.drill.categories.OperatorTest; |
| import org.apache.drill.common.exceptions.UserRemoteException; |
| import org.apache.drill.exec.planner.physical.PlannerSettings; |
| import org.apache.drill.exec.store.dfs.FileSystemConfig; |
| import org.apache.drill.exec.store.dfs.WorkspaceConfig; |
| import org.apache.drill.exec.util.StoragePluginTestUtils; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| |
| import java.nio.file.Paths; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| @Category(OperatorTest.class) |
| public class TestJoinEmptyDirTable extends JoinTestBase { |
| |
| private static final String EMPTY_DIRECTORY = "empty_directory"; |
| |
| @BeforeClass |
| public static void setupTestFiles() throws Exception { |
| dirTestWatcher.makeTestTmpSubDir(Paths.get(EMPTY_DIRECTORY)); |
| FileSystemConfig dfsConfig = (FileSystemConfig) getDrillbitContext() |
| .getStorage() |
| .getDefinedConfig(StoragePluginTestUtils.DFS_PLUGIN_NAME); |
| |
| String tmpWorkspacePath = dfsConfig.getWorkspaces() |
| .get(StoragePluginTestUtils.TMP_SCHEMA) |
| .getLocation(); |
| |
| // A tmp workspace with a default format defined for tests that need to |
| // query empty directories without encountering an error. |
| WorkspaceConfig tmpDefaultFormatConfig = new WorkspaceConfig( |
| tmpWorkspacePath, |
| true, |
| "csvh", |
| false |
| ); |
| dfsConfig.getWorkspaces().put("tmp_default_format", tmpDefaultFormatConfig); |
| } |
| |
| @Test |
| public void testHashInnerJoinWithLeftEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%s` t1 inner join cp.`employee.json` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(true, false, false); |
| testPlanMatchingPatterns(query, new String[]{HJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testHashInnerJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 inner join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(true, false, false); |
| testPlanMatchingPatterns(query, new String[]{HJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testHashInnerJoinWithBothEmptyDirTables() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%1$s` t1 inner join dfs.tmp_default_format.`%1$s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(true, false, false); |
| testPlanMatchingPatterns(query, new String[]{HJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testHashLeftJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 left join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 1155; |
| |
| enableJoin(true, false, false); |
| testPlanMatchingPatterns(query, new String[]{HJ_PATTERN, LEFT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testHashRightJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 right join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(true, false, false); |
| testPlanMatchingPatterns(query, new String[]{HJ_PATTERN, RIGHT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testMergeInnerJoinWithLeftEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%s` t1 inner join cp.`employee.json` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, true, false); |
| testPlanMatchingPatterns(query, new String[]{MJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testMergeInnerJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 inner join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, true, false); |
| testPlanMatchingPatterns(query, new String[]{MJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testMergeInnerJoinWithBothEmptyDirTables() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%1$s` t1 inner join dfs.tmp_default_format.`%1$s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, true, false); |
| testPlanMatchingPatterns(query, new String[]{MJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testMergeLeftJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 left join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 1155; |
| |
| enableJoin(false, true, false); |
| testPlanMatchingPatterns(query, new String[]{MJ_PATTERN, LEFT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testMergeRightJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 right join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, true, false); |
| testPlanMatchingPatterns(query, new String[]{MJ_PATTERN, RIGHT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testNestedLoopInnerJoinWithLeftEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%s` t1 inner join cp.`employee.json` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, false, true); |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testNestedLoopInnerJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 inner join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, false, true); |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testNestedLoopInnerJoinWithBothEmptyDirTables() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%1$s` t1 inner join dfs.tmp_default_format.`%1$s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, false, true); |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, INNER_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testNestedLoopLeftJoinWithLeftEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%s` t1 left join cp.`employee.json` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 0; |
| |
| enableJoin(false, false, true); |
| // See details in description for PlannerSettings.JOIN_OPTIMIZATION |
| setSessionOption((PlannerSettings.JOIN_OPTIMIZATION.getOptionName()), false); |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, LEFT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| resetSessionOption((PlannerSettings.JOIN_OPTIMIZATION.getOptionName())); |
| |
| } |
| } |
| |
| @Test |
| public void testNestedLoopLeftJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 left join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 1155; |
| |
| enableJoin(false, false, true); |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, LEFT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test |
| public void testNestedLoopRightJoinWithLeftEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from dfs.tmp_default_format.`%s` t1 right join cp.`employee.json` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| final int expectedRecordCount = 1155; |
| |
| enableJoin(false, false, true); |
| // The left output is less than right one. Therefore during optimization phase the RIGHT JOIN is converted to LEFT JOIN |
| testPlanMatchingPatterns(query, new String[]{NLJ_PATTERN, LEFT_JOIN_TYPE}, new String[]{}); |
| final int actualRecordCount = testSql(query); |
| assertEquals("Number of output rows", expectedRecordCount, actualRecordCount); |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| |
| @Test(expected = UserRemoteException.class) |
| public void testNestedLoopRightJoinWithRightEmptyDirTable() throws Exception { |
| try { |
| String query = String.format("select t1.`employee_id`, t1.`full_name`, t2.`employee_id`, t2.`full_name` " + |
| "from cp.`employee.json` t1 right join dfs.tmp_default_format.`%s` t2 on t1.`full_name` = t2.`full_name`", EMPTY_DIRECTORY); |
| |
| enableJoin(false, false, true); |
| // The nested loops join does not support the "RIGHT OUTER JOIN" logical join operator. |
| test(query); |
| } catch (UserRemoteException e) { |
| assertTrue("Not expected exception is obtained while performing the query with RIGHT JOIN logical operator " + |
| "by using nested loop join physical operator", |
| e.getMessage().contains("SYSTEM ERROR: CannotPlanException")); |
| throw e; |
| } finally { |
| resetJoinOptions(); |
| } |
| } |
| } |