/*
 * 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.store.parquet;

import org.apache.drill.PlanTestBase;
import org.apache.drill.categories.ParquetTest;
import org.apache.drill.categories.SlowTest;
import org.apache.drill.exec.util.StoragePluginTestUtils;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import java.nio.file.Paths;

import static org.junit.Assert.assertEquals;

@Category({ParquetTest.class, SlowTest.class})
public class TestParquetFilterPushdownWithTransitivePredicates extends PlanTestBase {

  private static final String TABLE_PATH = "parquetFilterPush/transitiveClosure";
  private static final String FIRST_TABLE_NAME = String.format("%s.`%s/%s`",
      StoragePluginTestUtils.DFS_PLUGIN_NAME, TABLE_PATH, "first");
  private static final String SECOND_TABLE_NAME = String.format("%s.`%s/%s`",
      StoragePluginTestUtils.DFS_PLUGIN_NAME, TABLE_PATH, "second");
  private static final String THIRD_TABLE_NAME = String.format("%s.`%s/%s`",
      StoragePluginTestUtils.DFS_PLUGIN_NAME, TABLE_PATH, "third");

  @BeforeClass
  public static void copyData() {
    dirTestWatcher.copyResourceToRoot(Paths.get(TABLE_PATH));
  }

  @Test
  public void testForSeveralInnerJoins() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 ON t1.`month` = t2.`month` " +
            "JOIN %s t3 ON t1.`period` = t3.`period` WHERE t2.`month` = 7 AND t1.`period` = 2",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 24;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=1", "second.*numRowGroups=1", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForFilterInJoinOperator() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 ON t1.`month` = t2.`month` AND t2.`month` = 7 " +
            "JOIN %s t3 ON t1.`period` = t3.`period` AND t1.`period` = 2",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 24;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=1", "second.*numRowGroups=1", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForLeftAndRightJoins() throws Exception {
    String query = String.format("SELECT * FROM %s t1 RIGHT JOIN %s t2 ON t1.`year` = t2.`year` " +
            "LEFT JOIN %s t3 ON t1.`period` = t3.`period` WHERE t2.`year` = 1987 AND t1.`period` = 1",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 54;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=2", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForCommaSeparatedJoins() throws Exception {
    String query = String.format("SELECT * FROM %s t1, %s t2, %s t3 WHERE t1.`year` = t2.`year` " +
            "AND t1.`period` = t3.`period` AND t2.`year` = 1990 AND t3.`period` = 1",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 24;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=2", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForInAndNotOperators() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 " +
            "ON t1.`year` = t2.`year` JOIN %s t3 ON t1.`period` = t3.`period` " +
            "WHERE t2.`year` NOT IN (1987, 1988) AND t3.`period` IN (1, 2)",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);


    int actualRowCount = testSql(query);
    int expectedRowCount = 72;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=6", "second.*numRowGroups=3", "third.*numRowGroups=4"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForBetweenOperator() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 " +
            "ON t1.`year` = t2.`year` JOIN %s t3 ON t1.`period` = t3.`period` " +
            "WHERE t2.`year` BETWEEN 1988 AND 1991 AND t3.`period` BETWEEN 2 AND 4 ",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 96;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=7", "second.*numRowGroups=5", "third.*numRowGroups=6"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForGreaterThanAndLessThanOperators() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 " +
            "ON t1.`year` = t2.`year` JOIN %s t3 ON t1.`period` = t3.`period` " +
            "WHERE t2.`year` >= 1990 AND t3.`period` < 2",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 36;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=3", "second.*numRowGroups=3", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }


  @Test
  public void testForSubQuery() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN " +
            "(SELECT `year`, `month` FROM %s WHERE `year` = 1987 AND `month` = 5) t2 " +
            "ON t1.`year` = t2.`year` AND t1.`month` = t2.`month`",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 4;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=1"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForWithStatement() throws Exception {
    String query = String.format("WITH `first_date` AS (SELECT `year`, `month` FROM %s WHERE `year` = 1987 and `month` = 5) " +
            "SELECT t2.`year`, t2.`month` FROM %s t2 JOIN `first_date` ON t2.`year` = `first_date`.`year` AND t2.`month` = `first_date`.`month`",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 4;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=1"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForTwoExists() throws Exception {
    String query = String.format("SELECT * from %s t1 " +
        " WHERE EXISTS (SELECT * FROM %s t2 WHERE t1.`year` = t2.`year` AND t2.`year` = 1988) " +
        " AND EXISTS (SELECT * FROM %s t3 WHERE t1.`period` = t3.`period` AND t3.`period` = 2)",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 2;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=2", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForFilterInHaving() throws Exception {
    String query = String.format("SELECT t1.`year`, t2.`year`, t1.`period`, t3.`period` FROM %s t1 " +
        "JOIN %s t2 ON t1.`year` = t2.`year` " +
        "JOIN %s t3 ON t1.`period` = t3.`period` " +
        "GROUP BY t1.`year`, t2.`year`, t1.`period`, t3.`period` " +
        "HAVING t2.`year` = 1987 AND t3.`period` = 1",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 1;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=2", "third.*numRowGroups=3"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test // TODO: CALCITE-2241
  @Ignore // For now plan has "first.*numRowGroups=16", "second.*numRowGroups=7"
  public void testForOrOperator() throws Exception {
    String query = String.format("SELECT * FROM %s t1 " +
            "JOIN %s t2 ON t1.`month` = t2.`month` " +
            "WHERE t2.`month` = 4 OR t1.`month` = 11",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 13;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=4", "second.*numRowGroups=2"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForInAndNotOperatorsInJoinCondition() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN %s t2 " +
            "ON t1.`year` = t2.`year` AND t2.`year` NOT IN (1987, 1988) JOIN %s t3 ON t1.`period` = t3.`period` " +
            "WHERE t3.`period` IN (1, 2)",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME, THIRD_TABLE_NAME);


    int actualRowCount = testSql(query);
    int expectedRowCount = 72;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=6", "second.*numRowGroups=3", "third.*numRowGroups=4"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test // TODO: CALCITE-2274
  @Ignore // For now plan has "first.*numRowGroups=16""
  public void testForSubQueryAndDynamicStar() throws Exception {
    String query = String.format("SELECT * FROM %s t1 JOIN " +
            "(SELECT * FROM %s WHERE `year` = 1987 AND `month` = 5) t2 ON t1.`year` = t2.`year` AND t1.`month` = t2.`month`",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 4;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=1"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test // TODO: CALCITE-2274
  @Ignore // For now plan has "second.*numRowGroups=7"
  public void testForWithStatementAndDynamicStar() throws Exception {
    String query = String.format("WITH `first_date` AS (SELECT * FROM %s t1 WHERE t1.`year` = 1987 and t1.`month` = 5) " +
            "SELECT * FROM %s t2 JOIN `first_date` ON t2.`year` = `first_date`.`year` AND t2.`month` = `first_date`.`month`",
        FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    int actualRowCount = testSql(query);
    int expectedRowCount = 4;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=2", "second.*numRowGroups=1"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test
  public void testForTransitiveFilterPushPastAgg() throws Exception {
    String query = String.format("SELECT t1.`year` FROM %s t1 WHERE t1.`month` = 7 AND t1.`period` = 2 AND t1.`month` IN " +
        "(SELECT t2.`month` FROM %s t2)", FIRST_TABLE_NAME, SECOND_TABLE_NAME);

    // Validate the plan
    int actualRowCount = testSql(query);
    int expectedRowCount = 2;
    assertEquals("Expected and actual row count should match", expectedRowCount, actualRowCount);

    final String[] expectedPlan = {"first.*numRowGroups=1", "second.*numRowGroups=1"};
    testPlanMatchingPatterns(query, expectedPlan);
  }

  @Test // DRILL-6371
  public void testForTransitiveFilterPushPastUnion() throws Exception {
    String query = "WITH year_total_1\n" +
      "     AS (SELECT c.r_regionkey    customer_id,\n" +
      "                1 year_total\n" +
      "         FROM   cp.`tpch/region.parquet` c\n" +
      "         UNION ALL\n" +
      "         SELECT c.n_nationkey    customer_id,\n" +
      "                1 year_total\n" +
      "         FROM   cp.`tpch/nation.parquet` c),\n" +
      "     year_total_2\n" +
      "     AS (SELECT c.r_regionkey    customer_id,\n" +
      "                1 year_total\n" +
      "         FROM   cp.`tpch/region.parquet` c\n" +
      "         UNION ALL\n" +
      "         SELECT c.n_nationkey    customer_id,\n" +
      "                1 year_total\n" +
      "         FROM   cp.`tpch/nation.parquet` c)\n" +
      "SELECT count(t_w_firstyear.customer_id) as ct\n" +
      "FROM   year_total_1 t_w_firstyear,\n" +
      "       year_total_2 t_w_secyear\n" +
      "WHERE  t_w_firstyear.year_total = t_w_secyear.year_total\n" +
      " AND t_w_firstyear.year_total > 0 and t_w_secyear.year_total > 0";

    // Validate the plan
    int actualRowCount = testSql(query);
    int expectedRowCount = 1;
    assertEquals("Expected and actual row count should match",
      expectedRowCount, actualRowCount);

    String[] excludedPlan = {"Filter"};
    testPlanMatchingPatterns(query, new String[0], excludedPlan);
  }
}

